package gbench.sandbox.jdbc;

import org.junit.jupiter.api.Test;

import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.NewTopic;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;

import java.text.NumberFormat;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.*;
import java.util.stream.*;

import gbench.common.matlib.MatlibCanvas.XVec;
import gbench.common.matlib.data.DataReader.DFrame;
import gbench.common.matlib.rdd.BasePair;
import gbench.common.matlib.rdd.PVec;
import gbench.common.tree.LittleTree.CronTime;
import gbench.common.tree.LittleTree.ExceptionalConsumer;
import gbench.common.tree.LittleTree.IRecord;
import gbench.common.tree.LittleTree.Jdbc;
import gbench.common.tree.LittleTree.Jdbc.JdbcConfig;
import gbench.common.tree.LittleTree.Jdbc.XJdbcSession;
import gbench.common.tree.LittleTree.SQL;
import gbench.commonApp.jdbc.IJdbcApp;
import gbench.commonApp.jdbc.ISqlDatabase;
import gbench.sandbox.jdbc.JunitERP2.FinanceApp.Price;
import gbench.sandbox.jdbc.JunitERP2.MyERPApp.FinanceEntity;
import gbench.sandbox.jdbc.JunitERP2.MyERPApp.LineItem;
import gbench.sandbox.jdbc.JunitERP2.MyERPApp.PriceQuote;
import gbench.sandbox.jdbc.JunitERP2.MyERPApp.Store.WareHouse;
import gbench.sandbox.jdbc.JunitERP2.FinanceApp.Money;

import static java.time.LocalDateTime.now;

import static gbench.common.matlib.rdd.BasePair.*;
import static gbench.common.matlib.MatlibCanvas.println;
import static gbench.common.tree.LittleTree.JOIN;
import static gbench.common.tree.LittleTree.kvp_int;
import static gbench.common.tree.LittleTree.trycatch;
import static gbench.common.tree.LittleTree.IRecord.A;
import static gbench.common.tree.LittleTree.IRecord.CONS;
import static gbench.common.tree.LittleTree.IRecord.L;
import static gbench.common.tree.LittleTree.IRecord.REC;
import static gbench.common.tree.LittleTree.IRecord.rb;
import static gbench.common.tree.LittleTree.Jdbc.XJdbcSession.OPEN;
import static gbench.common.tree.LittleTree.KVPair.KVP;
import static gbench.common.tree.LittleTree.SQL.nsql;
import static gbench.common.tree.LittleTree.Term.FT;

/**
 * 简单商品甲乙平台的模拟
 * @author Administrator
 *
 */
public class JunitERP2 {

    /**
     * 数据库的配置
     * @author gbench
     *
     */
    @JdbcConfig(url = "jdbc:mysql://localhost:3306/erp?serverTimezone=Asia/Shanghai", user = "root", password = "123456")
    interface MysqlDatabase extends ISqlDatabase { // 数据接口
    }

    /**
     * 数据库的操作
     * @author gbench
     *
     */
    public static class MyDataApp{
        
        public MyDataApp(Jdbc jdbc){
            this.jdbc = jdbc;
        }
        
        /**
         * 事务函数
         * @param cs
         */
        public void withTransaction(ExceptionalConsumer<? super XJdbcSession<UUID, Object, DFrame>> cs) {
            jdbc.withTransaction(OPEN(DFrame.dfmclc,cs));
        }
    
        /**
         * 
         * @param sql sql 语句
         * @return IRecord 流
         */
        public Stream<IRecord> sqlqueryS(final String sql){
            return jdbc.sql2records(sql).stream();
        }

        /**
         * 
         * @param sql
         * @return
         */
        public Optional<IRecord> sql2maybe(final String sql){
            return jdbc.sql2maybe(sql);
        }

        /**
         * 
         * @param entityTable
         * @param itemTable
         * @param entityId
         * @return
         */
        public Stream<IRecord> relqueryR(final String entityTable, final String itemTable, final Integer entityId) {
            
            return this.rel2df_sql_f(entityTable, itemTable)
                    .apply(FT("select * from tbl$ where entity_id=$0",entityId)).rowsS();
        }

        /**
         * 
         * @param entityTable
         * @param itemTable
         * @param entityId
         * @param itemId
         * @param drcr
         * @param quantity
         * @param optional
         */
        public void rel2insert(final String entityTable, final String itemTable, final Integer entityId, final Integer itemId, 
                final Integer drcr, final Double quantity, final Integer flag, final Number amount, final Number ref, final Object optional, final String description) {
            
                final var rec = REC( "optional", optional, 
                    "drcr", drcr,
                    "entity_id", entityId, 
                    "item_id", itemId, 
                    "quantity", quantity, 
                    "amount", amount,
                    "optional",optional,
                    "flag", flag,
                    "ref", ref,
                    "description", description,
                    "create_time", LocalDateTime.now());
                
                this.rel2insert_rec_f(entityTable, itemTable).apply(rec);
        }

        /**
         * 
         * @return  (tbl:表名,param:过滤的参数) -> stream
         */
        public BiFunction<String,IRecord,Stream<IRecord>> tbl2ss_sql_rec_bf(){
            return (tbl,params)->{
                final var where_line = params.stream(kvp->kvp.key()+"='"+kvp.value()+"'").collect(Collectors.joining(" and "));
                return jdbc.sql2records(FT("select * from $0",tbl,where_line.length()>0?where_line:"")).stream();
            };
        }
        
        /**
         * 
         * @return  (tbl:表名,param:过滤的参数) -> stream
         */
        public Function<IRecord,Stream<IRecord>> tbl2ss_rec_f(String tbl){
            return (params)->{
                final var where_line = params.stream(kvp->kvp.key()+"='"+kvp.value()+"'").collect(Collectors.joining(" and "));
                return jdbc.sql2records(FT("select * from $0",tbl,where_line.length()>0?where_line:"")).stream();
            };
        }
    
        /**
         * transaction中不能使用rel2insert_rec_f系列函数,会引发死锁,因为rel2insert_rec_f也会尝试锁表
         * @param tbl 表名
         * @return  (param:过滤的参数) -> maybe
         */
        public Function<IRecord,Optional<IRecord>> tbl2maybe_rec_f(final String tbl){
            return (params)->this.tbl2ss_sql_rec_bf().apply(tbl,params).findAny();
        }

        /**
         * 数据检索函数
         * @return  (tbl:表名,param:过滤的参数) -> maybe
         */
        public BiFunction<String,IRecord,Optional<IRecord>> tbl2maybe_tbl_rec_bf(){
            return (tbl,params)->this.tbl2ss_sql_rec_bf().apply(tbl,params).findAny();
        }

        /**
         * 实体明细查询: 明细表 占位符 tbl$
         * @param entityTbl 实体表
         * @param itemTbl 明细表
         * @return
         */
        public Function<String,DFrame> rel2df_sql_f(final String  entityTbl,final String itemTbl){
            final var tbl = FT("select * from t_entity_item_rel where name = '$0/$1'",entityTbl,itemTbl);
            return sql->{
                final var _sql = FT(sql,REC("tbl$","("+tbl+") tbl"));
                // println(_sql);
                final var d = jdbc.sql2records(_sql).stream().collect(DFrame.dfmclc);
                return d;
            };
        }
        
        /**
         * 实体明细查询: 明细表 占位符 tbl$
         * @param entityTbl 实体表
         * @param itemTbl 明细表
         * @return
         */
        public Function<String,Stream<IRecord>> rel2ss_sql_f(final String  entityTbl,final String itemTbl){
            final var tbl = FT("select * from t_entity_item_rel where name = '$0/$1'",entityTbl,itemTbl);
            return sql->{
                final var _sql = FT(sql,REC("tbl$","("+tbl+") tbl"));
                //println(_sql);
                final var d = jdbc.sql2records(_sql).stream();
                return d;
            };
        }
        
        /**
         * 实体明细查询: 明细表 占位符 tbl$
         * @param entityTbl 实体表
         * @param itemTbl 明细表
         * @return
         */
        public Function<IRecord,Stream<IRecord>> rel2ss_rec_f(final String  entityTbl,final String itemTbl){
            return rec->{
                final var sql = rec.stream(e->FT("$0='$1'",e._1(),e._2())).collect(Collectors.joining("and"));
                return this.rel2ss_sql_f(entityTbl, itemTbl).apply(sql);
            };
        }
        
        /**
         * 实体明细查询: 明细表 占位符 tbl$
         * @param entityTbl 实体表
         * @param itemTbl 明细表
         * @return
         */
        public Function<String,Optional<IRecord>> rel2maybe_sql_f(final String  entityTbl,final String itemTbl){
            return sql->{
                return this.rel2ss_sql_f(entityTbl, itemTbl).apply(sql).findAny();
            };
        }
    
        /**
         * 实体明细查询: 明细表 占位符 tbl$
         * @param entityTbl 实体表
         * @param itemTbl 明细表
         * @return
         */
        public Function<IRecord,Optional<IRecord>> rel2maybe_rec_f(final String  entityTbl,final String itemTbl){
            return rec->{
                final var where_line= rec.stream(e->FT("$0='$1'",e._1(),e._2())).collect(Collectors.joining(" and "));
                final var sql = FT("select * from tbl$ $0", rec.isEmpty()?"":FT("where $0",where_line));
                //println(sql);
                return this.rel2maybe_sql_f(entityTbl, itemTbl).apply(sql);
            };
        }

        /**
         *
         * @param entityTable
         * @param itemTable
         * @return
         */
        public Function<IRecord,IRecord> rel2insert_rec_f(final String entityTable, final String itemTable ) {
            
            return rec -> jdbc.withTransaction(OPEN(DFrame.dfmclc, sess -> {
                final var entityId = rec.get("entity_id");
                final var itemId = rec.get("item_id");
                final var drcr = rec.get("drcr");
                final var quantity = rec.get("quantity");
                final var optional = rec.get("optional");
                final var amount = rec.get("amount");
                final var flag = rec.get("flag");
                final var ref = rec.get("ref");
                final var description = rec.str("description");
                final var r = REC("path", "D:/sliced/files/products/valuables/gold.jpg", 
                        "name", FT("$0/$1", entityTable, itemTable), 
                        "optional", optional, 
                        "drcr", drcr, 
                        "entity_id", entityId,
                        "item_id", itemId, 
                        "quantity", quantity, 
                        "amount",amount,
                        "flag",flag,
                        "ref",ref,
                        "description", description, 
                        "create_time", LocalDateTime.now());
                final var sql = SQL.of("t_entity_item_rel", r).insert();
                // println(sql);
                sess.sql2execute2int(sql);
            }));
        }
    
        private Jdbc jdbc;
        
    }

    /**
     * 金融模块
     * @author gbench
     *
     */
    public static class FinanceApp {
        
        /**
         * 商品拥有 交换价值 和 使用价值。使用价值 对应 商品的 实物属性，而 交换价值 对应 商品的 财物属性 或者说 金融属性。
         * 在交易 或者 说 流通中 我们关注的是 交换价值 即 金融属性，而 非 使用价值。所以 商业 环境中所 说的价值 除了特别说明<br>
         * 都是 交换价值。<br>
         * 
         * 交易 是 在交易主体（卖方，买方） 之间 通过 在 交换价值 上的一致认同 来 重新分配 商品的过程。<br>
         * 即 卖方用 其支付品（一般为货币） 表示 交换价值 获得 卖方 商品表示的交换价值，这样 尽管在 交换价值上 等价 但 却在
         * 使用价值上 发生了 相互易主 即 卖方获得 商品 而 卖方 获得 支付品。 进而 实现了 重分配商品。
         * 
         * 传统交易 有一个缺点， 交易是一个时点概念，支付品商品的互换是在 一瞬之间给予完成的，也就是 交易双方必须 <br>
         * 同时 准备好 支付品和商品，然后一手交钱一手交货, 这对于 结构简单 ，数量 较少的 日常商品 的确不是问题，但是 对于大宗 货品 <br>
         * 或是 结构复杂 的产品（商品）就有些不可行了，比如 我支付8千万，够买1万吨玉米。把 1万吨玉米 和 8000万人民币 进行一手交钱 <br>
         * 一手交货就 很不现实，首先 场所就很是问题。 若是选择在 玉米粮仓，我们需要把 一车车的 货币 拉倒粮仓，要是 选择在 银行 你就需要 <br>
         * 把 粮食拉到银行 ，可以 肯定的是清点就很是问题， 无论在 粮仓里清点钞票 还是 在 银行中清点玉米 都不是一个 令人愉快的 工作。 <br>
         * 所以 把 交易只局限于一个 时点 极大的限制了 交易的 能力。因此 就必须 把 交易 扩展到 一个时段之中。于是 使用价值（物品）和 <br>
         * 交换价值（货币）的交易就可分开了，比如 你可以先把 粮食交给我，然后 我再把 货币交易给你。或是 我先把货款 支付给你 你再把粮食 <br>
         * 支付给我。于是 一种因 使用价值与交换价值的分类，而仅在交易过程出现的关系被构造出来了，债权债务关系，这是一种半个交易的交易的关系。
         * 即 一方业已支付给另一方，但他却并未从对方手中获得对方 做出的 货品 转让，也就是交易 只完成了一半，于是 我们就把 完成 商品转让的 一方
         * 叫做 债权方 意思是说 拥有命令对方 支付 相应价值的 权利， 未完成 商品转让的一方 叫做 债务方，意思是 背负一项任务或是支付任务<br>
         * 将相应额价值、货品 交付给 另一方（债权方）。
         * 
         * 但是 这种 半个交易 的说法实在 让人觉得 有点不舒服，于是 人们 就 给他做了一个 补充，即 对于 已经支付的一方（债权方） 来说，<br>
         * 未曾支付（债务方）的 一方也被视为做了一种支付，即 信用支付，也就是说 它承诺自己，一段时间以后会给与支付的，或者说 债务方使用了 <br>
         * 债权方 专门为自己 创立的一种 虚拟货币,信用货币（这种虚拟货币需要一段时间后用真实货币来给与置换） 来给与支付。我们 把这种 债权方 专门 <br>
         * 为 债务方 发行 虚拟货币 叫做 信用。可以 这样认为 债务方 就好像 在 债权方 所 管理的一个信用银行 开了一个 账户，当交易发生时，即 <br>
         * 构造出 本债权债务关系时，债权方 使用了 自己 在其（信用账户) 来给予支付。当然这种 信用账户 是一种 临时结算账户，必然要在未来的 <br>
         * 一段时间中给予进行实际货币的支付。<br>
         * 
         * 其实 信用（货币）概念 的产生，是可以 突破 单个商品交易的，即 他可以跨越 交易 重新 组织 债权债务关系，在 一笔交易中 你是债务方我是债权方
         * 在第二笔交易中我是债权方你又是债务方，...., 由于 我们之间有发生着 多个 商品交易，于是 我们 势必分别为对方 进行授信和被授信（建立信用账户）
         * 于是 这些交易 就可以 重新的 给与 组合起来 形成一个 进行 金融交易（即没有使用价值即商品交易发生的交易），仅仅 结算 债权债务关系的 交易。
         * 一方需要给另一方 支付 多少使用价值。这就是 信用所 创造出的 虚拟经济的 根本原理。 
         * 
         * 
         * 所谓信用就是 商品提供方 对 商品采购方的 支付能力的 评估 <br>
         * 即 在本次交易中 采购方 是否能够 提供 或 是给予 倾注 足够的 价值 付出&成本 <br.
         * 来获取采购的商品。 <br>
         * 需要注意： <br>
         * 1）信用是针对于 交易而言的，即 是 债权方（商品提供方），对 债务方（商品接收方）的 偿还能力的评估 ,<br>
         * 本质上 与 债务方的 真实 财务实力 没有必然即关系，即 假若 债务方 对交易 毫无专心/重视/坦诚观念 比如 <br>
         * 以 一种 欺诈 或是 应付 的 心态 来 获取 商品，那么 尽管他 有足够的资金储备，但是 并未 在 本次交易中
         * 配置、留有 足够的 价值支付能力 来 结算交易 因此 他的 信用值（即用于偿还计算债务的能力)还是不足的。
         * 2）信用 是一种 行为能力，即债务偿还能力的评估，所谓债务是 对 在交易中所 因获取的商品而 负有的 <br>
         * 与该商品在价值量有相应对应的 其他价值的 支付义务。具体 价值量 是什么，需要采用何种实物进行表达<br>
         * 则是需要通过交易合约（联合约定）来给予确定。<br>
         * 
         * @author gbench
         *
         * @param <T> 信用值 即 体现价值的具体实物。
         */
        public interface Credit<T> {
            
            /**
             * 信用值
             * @return 价值量大小。
             */
            public Double getValue();
            
            /**
             * 价值物
             * @return 信用对象
             */
            public T getUnderlying();
            
            /**
             * underlying 的比率 <br>
             * 用U来衡量T 的价值量 <br>
             * this.underlying / t.underlying 的 比率 <br>
             * 
             * @param credit 信用比率
             * @return this/t
             */
            public <U> Double ratioOf(final Credit<U> credit);

            /**
             * 获取属性值
             * @return
             */
            public IRecord getAtrributes();
            
        }
        
        /**
         * 货币
         * 
         * @author Administrator
         *
         */
        public class Money extends Entity<Money> implements Credit<Money> {
            
            /**
             * 货币对象
             * 
             * @param id   货币id
             */
            public Money(final Integer id) {
                this.id = id;
            }

            /**
             * 货币对象
             * 
             * @param id   货币id
             * @param name 货币名称
             */
            public Money(final Integer id, final String name) {
                this.id = id;
                this.name = name;
            }
            
            /**
             * 对当前的货币 采用 money 进行计价。
             * 
             * @param money 计价的货币
             * @return Price(ratio,money)
             */
            public <U> Double ratioOf(final Credit<U> money) {
                final var p0 = this.attributes.dbl("price"); // 相对于本位币的价格
                final var p1 = money.getAtrributes().dbl("price"); // 相对于本位币的价格
                final var ratio = p0 / p1;
                return ratio;
            }

            /**
             * 对当前的货币 采用 money 进行计价。
             * 
             * @param money 计价的货币
             * @return Price(ratio,money)
             */
            public Price priceOf(final Credit<Money> money) {
                final var ratio = this.ratioOf(money);
                return new Price(ratio, (Money) money);
            }

            /**
             * 是否是本位币
             * 
             * @return 本位币,true,other false
             */
            public boolean isStandard() {
                return this.getAttributes().dbl("flag") > 0;
            }

            @Override
            public Double getValue() {
                return 1d;
            }

            @Override
            public Money getUnderlying() {
                return this;
            }

            @Override
            public IRecord getAtrributes() {
                return this.attributes;
            }
            
            /**
             * hashCode
             */
            public int hashCode() {
                return Objects.hash(this.id,this.name,this.attributes);
            }
            
            /**
             * 等价判断
             */
            public boolean equals(final Object obj) {
                if (!(obj instanceof Money)) return false;
                final var money = (Money) obj;
                return this.id.equals(money.getId());
            }
        }
    
        /**
         * 静态概念：价格对象<br>
         * 动态概念：计价对象(this_price),用于对另外一个价格对象another_price 采用 <br>
         * this_price 的货币this_money进行计价。this_price.getPrice(another_price);
         * 
         * @author Administrator
         *
         */
        public class Price {
            
            /**
             * 生成一个计价对象
             * 
             * @param amount 数量
             * @param money  计价货币
             */
            public Price(final Number amount, final Money money) {
                this.amount = amount.doubleValue();
                this.money = money;
            }
            
            /**
             * 金额对象
             * 
             * @return 提取金额对象
             */
            public Double getAmount() {
                return this.amount;
            }
    
            /**
             * 货币
             * 
             * @return 提取货币对象
             */
            public Money getMoney() {
                return this.money;
            }
            
            /**
             * 把 another_price 对象采用 本价格对象(this)的 计价货币进行重新计价。<br>
             * 注意 another_price对象 和 本价格对象(this) 是两个对象, 他们可以分别采用 不同的货币 进行计价。
             * 
             * @param another_price 待计价的 价格对象
             * @return 把 another_price对象采用 本价格对象(this)的 计价货币进行重新计价。
             */
            public Price reprice(final Price another_price) {
                final var this_money = this.getMoney();
                final var another_price_money_of_this_price = another_price.getMoney().priceOf(this_money); // price 的 this_money的价格
                return another_price_money_of_this_price.mul(another_price.getAmount()); // 把 another_price对象采用 本价格对象(this)的 计价货币进行重新计价。
            }
            
            /**
             * reprice 的别名 <br>
             * 把 another_price 对象采用 本价格对象(this)的 计价货币进行重新计价。<br>
             * 注意 another_price对象 和 本价格对象(this) 是两个对象, 他们可以分别采用 不同的货币 进行计价。
             * 
             * @param another_price 待计价的 价格对象
             * @return 把 another_price对象采用 本价格对象(this)的 计价货币进行重新计价。
             */
            public Price getPrice(final Price another_price) {
                return this.reprice(another_price);
            }
            
            /**
             * 对当前price采用货币money重新计价
             * @param money 计价货币
             * @return 货币重新计价
             */
            public Price priceOf(final Money money) {
                return new Price(1,money).getPrice(this);
            }
            
            /**
             * 价格乘法
             * @param price 价格数量
             * @return 价格
             */
            public Price mul(final Number price) {
                final var _amount = this.amount*price.doubleValue();
                return new Price(_amount,this.money);
            }
            
            /**
             * 价格乘法
             * @param price 价格对象
             * @return 价格
             */
            public Price mul(final Price price) {
                final var _amount = this.amount*this.getPrice(price).getAmount();
                return new Price(_amount,this.money);
            }
            
            /**
             * 价格加法
             * @param price 价格数量
             * @return 价格
             */
            public Price plus(final Number price) {
                final var _amount = this.amount + price.doubleValue();
                return new Price(_amount, this.money);
            }
            
            /**
             * 价格加法
             * @param price 价格数量
             * @return 价格
             */
            public Price plus(final Price price) {
                final var _amount = this.amount+this.getPrice(price).getAmount();
                return new Price(_amount,this.money);
            }
            
            /**
             * 价格加法
             * @param price 价格数量
             * @return 价格
             */
            public Price minus(final Price price) {
                final var _amount = this.amount-this.getPrice(price).getAmount();
                return new Price(_amount,this.money);
            }
            
            /**
             * 价格加法
             * @param price 价格数量
             * @return 采用price作为计价货币的产品价格。
             */
            public Price divide(final Price price) {
                final var _amount = this.amount / this.getPrice(price).getAmount();
                final var finance = FinanceApp.this; // 金融模块
                final var new_money_name = FT("{新货币-[$0*$1]-$2}", price.amount, price.getMoney(), UUID.randomUUID());
                final var new_price = price.priceOf(finance.getStandardMoney()).getAmount(); // 提取交易比率
                return new Price(_amount, finance.createMoney(REC("name", new_money_name, "price", new_price)));
            }
            
            /**
             * 
             */
            public String toString() {
                return FT("$0 $1",this.money,this.amount);
            }
    
            private Double amount;
            private Money money;
        }
        
        /**
         * MyERPApp 的初始化
         */
        public void initialize(final Stream<IRecord> moneyList) {
            println("初始化货币环境");
            final var moneys = moneyList // 提取系统货币
                .map(e -> {
                    final var money = new Money(e.i4("id"), e.str("name"));
                    final var kvp = KVP(e.str("name"), money);
                    money.setAttributes(e); // 为对象增加属性。
                    return kvp; // 属性对儿
                }).collect(IRecord.rclc2);
            
            // 注册货币
            moneys.foreach((String symbol,Money money)->{
                this.registMoney(money);
                if(money.isStandard()) this.standardMoney = money; // 设置本位币
                println(money.isStandard()?"*":"-",money,money.getId());
            });
            this.moneyIdPool.set(moneyRegistry.size()+1);
            
            println("-------------------------------------------------------------------------");
            println("汇率换算");
            final var USD = this.USD();
            final var CNY = this.CNY();
            final var JPY = this.MONEY("日元");
            println("USD/CNY",USD.priceOf(CNY),CNY);
            println("CNY/USD",CNY.priceOf(USD),USD);
            println("JPY/CNY",JPY.priceOf(CNY),CNY);
            println("USD/JPY",USD.priceOf(JPY),JPY);
            println("10CNY+10USD:",this.rmb(10).plus(this.dollar(10)),"CNY");
            println("10USD+10CNY:",this.dollar(10).plus(this.rmb(10)),"USD");
            println("10JPY+10USD:",this.price(10, JPY).plus(this.dollar(10)),"JPY");
            final var dollar100 = this.price(100, this.USD()); // 100美元
            final var rmb10 = this.price(10,this.CNY()); // 10 元的人民币
            final var new_price = dollar100.divide(rmb10); // 新价格
            println("-------------------------------------------------------------------------");
            println("dollar100以rmb10进行计价",new_price,
                    "\n新货币转换到美元",new_price.priceOf(this.USD()),
                    "\n新货币转换到人民币",new_price.priceOf(this.CNY()),
                    "\n新货币转换到日元",new_price.priceOf(this.MONEY("日元"))
            );// println
            println("-------------------------------------------------------------------------");
        }
        
        /**
         * 注册货币
         * @param money
         */
        public void registMoney(Money money) {
            moneyRegistry.set(money.getName(),money);
            moneyTable.put(money.getId(), money);
        }
        
        /**
         * 人民币计价
         * @param amount
         * @return
         */
        public Price rmb(final Number amount) {
            return this.price(amount.doubleValue(),this.CNY());
        }
        
        /**
         * 美元计价
         * @param amount
         * @return
         */
        public Price dollar(final Number amount) {
            return this.price(amount.doubleValue(),this.USD());
        }
        
        /**
         * 制造一个价格
         * @param amount
         * @param money
         * @return
         */
        public Price price(final Number amount, final Money money) {
            return new Price(amount.doubleValue(),money);
        }
    
        /**
         * 
         * @param id
         * @return
         */
        public Money getMoneyById(int id) {
            return this.moneyTable.get(id);
        }
        
        /**
         * 获取新的货币Id
         * @return 货币价格。
         */
        public Long getNextMoneyId() {
            return this.moneyIdPool.getAndIncrement();
        }
        
        /**
         * 创建一种货币
         * 
         * @param rec 货币信息:name 名称,price 与本位币的兑换比例,symbol 货币符号, label 货币简写,create_time 创建时间。
         * @return 货币对象。
         */
        public Money createMoney(final IRecord rec) {
            final var id = this.getNextMoneyId(); // 分配货币号
            final var name = rec.str("name");
            final var money = new Money(id.intValue(), name);
            money.setAttributes(rec);
            return money;
        }
        
        /**
         * 获得本位币
         * @return
         */
        public Money getStandardMoney() {
            return this.standardMoney;
        }
        
        /**
         * 
         * @return
         */
        public Money CNY() {
            return moneyRegistry.get("人民币",Money.class);
        }
    
        /**
         * 
         * @return
         */
        public Money USD() {
            return moneyRegistry.get("美元",Money.class);
        }
    
        /**
         * 提取货币
         * @return
         */
        public Money MONEY(String name) {
            return moneyRegistry.get(name,Money.class);
        }
    
        /**
         * 系统货币
         */
        public final IRecord moneyRegistry = REC(); // 系统的货币集合
        public final Map<Integer,Money> moneyTable = new HashMap<Integer,Money>(); // 系统的货币集合
        public AtomicLong moneyIdPool = new AtomicLong(0); // 货币ID池
        public Money standardMoney; // 货币ID池
        
    }
    
    class KafkaApp {

        /**
         * 
         */
        public KafkaApp() {
            this("127.0.0.1:9092", "quickstart-events");
        }

        /**
         * 
         * @param mq_address_collection
         * @param topic
         */
        public KafkaApp(String mq_address_collection, String topic) {
            super();
            this.mq_address_collection = mq_address_collection;
            this.topic = topic;
        }

        /**
         * 
         * @author gbench
         *
         */
        class Consumer {

            /**
             * 初始化消费者
             */
            public Consumer() {
                this.consumer_topic = KafkaApp.this.topic;
                Properties configs = initialize();
                consumer = new KafkaConsumer<String, String>(configs);
                consumer.subscribe(Arrays.asList(this.consumer_topic));
            }

            /**
             * 初始化配置
             */
            public Properties initialize() {
                Properties props = new Properties();
                props.put("bootstrap.servers", KafkaApp.this.mq_address_collection);
                props.put("group.id", this.consumer_group_id);
                props.put("enable.auto.commit", this.consumer_enable_auto_commit);
                props.put("auto.commit.interval.ms", this.consumer_auto_commit_intervals_ms);
                props.put("session.timeout.ms", this.consumer_session_timeout_ms);
                props.put("max.poll.records", this.consumer_max_poll_records);
                props.put("auto.offset.reset", "earliest");
                props.put("key.deserializer", StringDeserializer.class.getName());
                props.put("value.deserializer", StringDeserializer.class.getName());
                return props;
            }

            /**
             * 
             */
            public void run() {
                while (true) {
                    ConsumerRecords<String, String> records = consumer.poll(this.consumer_poll_time_out);
                    records.forEach((ConsumerRecord<String, String> record) -> {
                        println("key", record.key(), "value", record.value(), "topic", record.topic());
                    });
                }
            }

            private final String consumer_topic; // 消费者连接的topic
            private final String consumer_group_id = "1"; // groupId，可以分开配置
            private final String consumer_enable_auto_commit = "true"; // 是否自动提交（消费者）
            private final String consumer_auto_commit_intervals_ms = "1000";
            private final String consumer_session_timeout_ms = "30000"; // 连接超时时间
            private final int consumer_max_poll_records = 10; // 每次拉取数
            private final Duration consumer_poll_time_out = Duration.ofMillis(3000); // 拉去数据超时时间
            private final KafkaConsumer<String, String> consumer;
        }

        /**
         * 
         * @author gbench
         *
         */
        class Producer {

            /*
             * 初始化生产者
             */
            public Producer() {
                this.producer_topic = KafkaApp.this.topic;
                Properties configs = initialize();
                producer = new KafkaProducer<String, String>(configs);
            }

            /*
             * 初始化配置
             */
            Properties initialize() {
                Properties props = new Properties();
                props.put("bootstrap.servers", KafkaApp.this.mq_address_collection);
                props.put("acks", "all");
                props.put("retries", 0);
                props.put("batch.size", 16384);
                props.put("key.serializer", StringSerializer.class.getName());
                props.put("value.serializer", StringSerializer.class.getName());
                return props;
            }
            
            /**
             * 
             * @param topic
             * @param rec
             */
            public void send(final String topic, final IRecord rec) {
                final var record = new ProducerRecord<String, String>(this.producer_topic,rec.json(), System.nanoTime() + "");
                this.producer.send(record);
            }
            
            /**
             * 
             * @param rec
             * @param rec
             */
            public void send(final IRecord rec) {
                final var record = new ProducerRecord<String, String>(this.producer_topic,LocalDate.now()+"", rec.json());
                this.producer.send(record);
            }

            /**
             * 
             * @throws InterruptedException
             */
            public void run() throws InterruptedException {
                // 消息实体
                ProducerRecord<String, String> record = null;
                for (int i = 0; i < 10; i++) {
                    record = new ProducerRecord<String, String>(this.producer_topic,LocalDate.now().toString(), System.nanoTime() + "");
                    println(record);
                    // 发送消息
                    producer.send(record, new Callback() {
                        @Override
                        public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                            if (null != e) {
                                println("send error" + e.getMessage());
                            } else {
                                println(String.format("offset:%s,partition:%s", recordMetadata.offset(),
                                        recordMetadata.partition()));
                            } // if
                        }
                    });
                }
                producer.close();
            }
            
            /**
             * 
             */
            public void close() {
                this.producer.close();
            }

            private final String producer_topic; // 生产者连接的topic
            private final KafkaProducer<String, String> producer;
        }
        
        /**
         * 
         * @author gbench
         *
         */
        public class Admin{
            
            public Admin(){
                this.initialize();
            }
            
            public void initialize() {
                Properties properties = new Properties();
                properties.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, mq_address_collection);
                client = AdminClient.create(properties);
            }
            
            /**
             * 创建一个主体
             * @param topics
             */
            public void createTopic(final String ... topics) {
                try {
                   final var b =  client.listTopics().listings().get().stream().map(e->e.name()).filter(topic::equals).findFirst().isEmpty();
                   if(b) {
                       client.createTopics(L(new NewTopic(topic,1,(short)1) ));
                   }
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            }
            
            
            /**
             * 删除主题
             */
            public void deleteTopics(final String ... topics) {
                try {
                    final var tps = L(topics);
                    final var _topics = client.listTopics().listings().get().stream().map(e->e.name()).filter(tps::contains).collect(Collectors.toList());
                    if(_topics.size()>0) {
                        client.deleteTopics(_topics);
                    }
                 } catch (InterruptedException | ExecutionException e) {
                     e.printStackTrace();
                 }
            }
            
            /**
             * 获取主体
             */
            public List<String> getTopics() {
                final var ll = new LinkedList<String>();
                try {
                    final var dd = client.listTopics().listings().get().stream().map(e->e.name());
                    ll.addAll(dd.collect(Collectors.toList()));
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
                
                return ll;
            }
            
            public void close() {
                this.client.close();
            }
            
            private AdminClient client;
        }

        public final String mq_address_collection; // kafka地址
        public final String topic;

    }

    /**
     * 简单实体
     * @author gbench
     *
     */
    public static class Entity <S>{

        /**
         * 
         * @return
         */
        public String getName() {
            return this.name;
        }

        /**
         * 
         */
        public String toString() {
            return this.name;
        }

        public Integer getId() {
            return this.id;
        }
        
        /**
         * 提取属性值
         * 
         * @param <T>
         * @param name 属性名
         * @param defaultValue 默认值
         * @return 属性值
         */
        public <T> T attr(final String name,T defaultValue) {
            return attributes.get(name, defaultValue);
        }
        
        /**
         * @return the attributes
         */
        public IRecord getAttributes() {
            return attributes;
        }

        /**
         * @param attributes the attributes to set
         */
        @SuppressWarnings("unchecked")
        public S setAttributes(final IRecord attributes) {
            this.attributes = attributes;
            return (S)this;
        }
        
        /**
         * 探求哈希吗
         */
        public int hashCode() {
            return this.id.hashCode();
        }
        
        /**
         * 判断对象是否相等
         */
        public boolean equals(final Object obj) {
            if (obj == null)
                return false;
            final var cls = this.getClass();
            if (cls != obj.getClass()) {
                return false;
            } else {
                return this.getId().equals(((Entity<?>) obj).getId());
            } // if
        }
        
        protected Integer id;
        protected String name;
        protected String description;
        protected IRecord attributes;
    }

   

    /**
     * ERP App
     * @author gbench
     *
     */
    public static class MyERPApp {
        
        /**
         * 平台初始化
         * @param jdbc
         */
        public MyERPApp(final Jdbc jdbc){
            this.jdbc = jdbc;
            financeApp = new FinanceApp();
            dataApp = new MyDataApp(jdbc);
            this.initialize();
        }
        
        /**
         * 行项目 <br>
         * 订单项目
         * 
         * @author Administrator
         *
         */
        public class LineItem {
            /**
             * 行项目
             * 
             * @param product 产品对象
             * @param quantity 产品数量
             */
            public LineItem(final Product product, final Number quantity) {
                this.product = product;
                this.quantity = quantity.doubleValue();
                this.unitPrice = product.getPrice();
            }

            /**
             * 行项目 <br>
             * 
             * @param priceQuote 产品报价
             * @param quantity   产品数量
             */
            public LineItem(final PriceQuote priceQuote, final Number quantity) {
                this.product = priceQuote.getProduct();
                this.quantity = quantity.doubleValue();
                this.unitPrice = priceQuote.getUnitPrice();
            }

            /**
             * 名称
             * 
             * @return 名称
             */
            public String getName() {
                return FT("$0", this.product.getName());
            }

            /**
             * 价格总结
             * 
             * @return
             */
            public Price getTotal() {
                return this.unitPrice.mul(this.quantity);
            }

            /**
             * @return the product
             */
            public Product getProduct() {
                return product;
            }

            /**
             * 数量
             * 
             * @return the quantity
             */
            public Double getQuantity() {
                return quantity;
            }

            /**
             * 单价
             * 
             * @return the unitPrice
             */
            public Price getUnitPrice() {
                return unitPrice;
            }

            /**
             * 税费
             * 
             * @return the tax
             */
            public Price getTax() {
                return tax;
            }

            /**
             * 格式化输出
             */
            public String toString() {
                return FT("$0\t$1\t$2", product.getName(), this.quantity, this.getTotal());
            }
            
            /**
             * hashCode
             */
            public int hashCode() {
                final var code = Objects.hash(product,quantity,unitPrice,tax);
                return code;
            }
            
            /**
             * 
             */
            public boolean equals(final Object obj) {
                if (!(obj instanceof LineItem)) return false;
                final var o = (LineItem) obj;
                
                return (product == o.product || product.equals(o.product))
                        && (quantity == o.quantity || quantity.equals(o.quantity))
                        && (unitPrice == o.unitPrice || unitPrice.equals(o.unitPrice))
                        && (tax == o.tax || tax.equals(o.tax));
            }

            private Product product;
            private Double quantity;
            private Price unitPrice;
            private Price tax;
        }

        /**
         * 产品列表
         * 
         * @author Administrator
         *
         */
        public class Product extends Entity<Product> {
            
            /**
             * 产品记录
             * @param rec 产品记录
             */
            public Product(final IRecord rec) {
                this(rec.i4("id"),rec.str("name"),rec.dbl("price"),rec.i4("money"));
                this.setAttributes(rec);
            }

            /**
             * 基础构造函数
             * 
             * @param id 产品id
             * @param name 产品名称
             * @param price 产品价格
             * @param moneyId 产品计价货币
             */
            public Product(final Integer id, final String name, final Double price,final int moneyId) {
                this.id = id;
                this.name = name;
                this.price = price;
                this.money = financeApp.getMoneyById(moneyId);
            }
            
            /**
             * 
             * @return
             */
            public Price getPrice() {
                return financeApp.new  Price(this.price,money);
            }
            
            /**
             * 
             * @return
             */
            public String getBrand() {
                return this.brand;
            }
            
            /**
             * 
             * @return
             */
            public String getQuality() {
                return this.quality;
            }
            
            /**
             * hashCode
             */
            public int hashCode() {
                final var code = Objects.hash(price,money,brand,quality);
                return code;
            }
            
            /**
             * 等价关系
             */
            public boolean equals(Object obj) {
                if (!(obj instanceof Product))
                    return false;
                final var o = (Product) obj;
                var b = false;

                b = this.price == o.price && (money == o.money || money.equals(o.money))
                        && (brand == o.brand || brand.equals(o.brand))
                        && (quality == o.quality || quality.equals(o.quality));

                return b;
            }

            private double price;
            private Money money;
            private String brand;
            private String quality;
        }

        /**
         * 报价单
         * 
         * @author Administrator
         *
         */
        public class PriceQuote {
            
            /**
             * 
             * @param product
             * @param unitPrice
             */
            public PriceQuote(final Product product,final Price unitPrice){
                this.product = product;
                this.unitPrice = unitPrice;
            }
            /**
             * @return the product
             */
            public Product getProduct() {
                return product;
            }
            /**
             * @return the unitPrice
             */
            public Price getUnitPrice() {
                return unitPrice;
            }
            /**
             * @return the descrition
             */
            public String getDescrition() {
                return descrition;
            }
            /**
             * @return the version
             */
            public String getVersion() {
                return version;
            }
            
            /**
             * 
             */
            public String toString() {
                return FT("$0#$1 : $2", this.product.id, this.product.getName(), this.unitPrice);
            }
            
            /**
             * hashCode
             */
            public int hashCode() {
                return Objects.hash(product,unitPrice,descrition,version);
            }
            
            /**
             * 
             */
            public boolean equals(final Object obj) {
                if (!(obj instanceof PriceQuote))
                    return false;
                final var o = (PriceQuote) obj;
                return (product == o.product || product.equals(o.product))
                        && (unitPrice == o.unitPrice || unitPrice.equals(o.unitPrice))
                        && (descrition == o.descrition || descrition.equals(o.descrition))
                        && (version == o.version || version.equals(o.version));
            }
            
            private Product product;
            private Price unitPrice;
            private String descrition;
            private String version;
        }

        /**
         * 状态对象
         * @author gbench
         *
         */
        public class Status extends Entity<Status> {
            
            /**
             * 状态对象
             * @param id 状态id
             */
            public Status(final Integer id) {
                this.id = id;
                switch (id) {
                case 0:
                    this.name = "创建";
                    break;
                case 1:
                    this.name = "已审核";
                    break;
                case 2:
                    this.name = "待执行";
                    break;
                case 3:
                    this.name = "已完成";
                    break;
                case 4:
                    this.name = "已结算";
                    break;
                default:
                    this.name = "";
                }
            }
        }

        /**
         * 库存单
         * 
         * @author Administrator
         *
         */
        public class StoreBill<S> {

            /**
             * 
             * @param order
             */
            public StoreBill(final Order<?> order) {
                this.order = order;
                this.lineItems = order.getLineItems(true);
            }

            /**
             * 
             * @param order
             */
            public StoreBill(final Order<?> order, final List<LineItem> lineItems) {
                this.order = order;
                this.lineItems = lineItems;
            }

            /**
             * 仓储单
             * 
             * @param ratios 发货/收货比率
             * @return 仓储单
             */
            public <U> U map(final Function<StoreBill<?>, U> mapper, final Double... ratios) {
                final var n = ratios.length;
                final var lineItems = this.lineItems.stream().map(kvp_int()).map(p -> {
                    final var originItem = p._2();
                    final var qty = originItem.quantity * ratios[p._1() % n];
                    final var item = new LineItem(originItem.product, qty);
                    return item;
                }).collect(Collectors.toList());
                final var sb = new StoreBill<>(this.order, lineItems);
                return mapper.apply(sb);
            }

            /**
             * 
             * @param base
             * @return
             */
            public XVec<BasePair<LineItem, LineItem>> baseline(final Order<?> base) {
                return XVec.OF(base.lineItems).zip(this.lineItems, BasePair::bp);
            }

            /**
             *
             * @return
             */
            public XVec<BasePair<LineItem, LineItem>> baseline() {
                return XVec.OF(this.order.lineItems).zip(this.lineItems, BasePair::bp);
            }

            /**
             * 
             * @return
             */
            public List<LineItem> getLineItems() {
                return this.lineItems;
            }

            /**
             * 返回订单对象
             * 
             * @return
             */
            public Order<?> getOrder() {
                return this.order;
            }
            
            /**
             * 
             * @param owner
             * @return
             */
            @SuppressWarnings("unchecked")
            public S setOwner(final Enterprise owner) {
                this.owner = owner;
                return (S) this;
            }

            /**
             * 
             * @return
             */
            public Enterprise getOwner() {
                return this.owner;
            };

            /**
             * 
             */
            public String toString() {
                return this.order.toString();
            }

            private Order<?> order;
            private List<LineItem> lineItems;
            private Enterprise owner;
        }

        /**
         * 
         * @author Administrator
         *
         */
        public class InboundBill extends StoreBill<InboundBill> {

            public InboundBill(final StoreBill<?> bill) {
                super(bill.order, bill.lineItems);
                this.setOwner(bill.getOwner());
            }

        }

        /**
         * 
         * @author Administrator
         *
         */
        public class OutboundBill  extends StoreBill<OutboundBill> {
        
            public OutboundBill(final StoreBill<?> bill) {
                super(bill.order,bill.lineItems);
                this.setOwner(bill.getOwner());
            }
            
        }

        /**
         * 发票
         * 
         * @author Administrator
         *
         */
        public class Invoice {
            /**
             * @return the lineItems
             */
            public List<LineItem> getLineItems() {
                return lineItems;
            }

            /**
             * @param lineItems the lineItems to set
             */
            public void setLineItems(List<LineItem> lineItems) {
                this.lineItems = lineItems;
            }
            
            private List<LineItem> lineItems;
        }

        /**
         * 产品目录
         * @author gbench
         *
         */
        public class Catalog extends Entity<Catalog> implements Iterable<PriceQuote>{
            
            /**
             * 产品目录
             * 
             * @param rec(id:id,name:名称)
             */
            public Catalog(final IRecord rec) {
                this(rec.i4("id"), rec.str("name"));
            }

            /**
             * 基本构造函数
             * 
             * @param id   id
             * @param name 名称
             */
            public Catalog(final Integer id, final String name) {
                this.id = id;
                this.name = name;
            }
            
            /**
             * 保存为价格目录
             *
             * @param quoteItems 产品报价 [{product,price,tax}]
             * 
             * @return Catalog 本身
             */
            public Catalog add(final Iterable<IRecord> quoteItems) {
                final Integer ownerId = this.getOwnerId();
                
                if (ownerId == null) {
                    System.err.println(FT("无法获得属主id,catalog 无效：$0", this.attributes));
                } else {
                    final var owner  = MyERPApp.this.getEnterprise(this.getOwnerId());// 提取owenr 对象
                    quoteItems.forEach(quoteItem -> { // 把产品价格加入到 产品目录。
                        final var pct = quoteItem.get("product", Product.class);
                        final var price = quoteItem.dbl("price", pct.getPrice().getAmount());
                        final var tax = quoteItem.dbl("tax", 0.17);
                        final var opt = dataApp.rel2maybe_rec_f("t_product", "t_money").apply(REC(
                            "entity_id", pct.getId(), "item_id", 1, "optional", this.getId(), "drcr", ownerId) // item_id 对应于 moneyid
                        ); // 检查价格目录的产品项目是否存在,optional为价格目录编号,entity_id 对应产品id
                        
                        // println(opt);
                        if (opt.isEmpty()) { // 加入报价单
                            // 报价单的创建 其中 amount 是作为 税率来出现的
                            dataApp.rel2insert_rec_f("t_product", "t_money").apply(REC(
                                /* 产品id */ "entity_id", pct.getId(), /* 货币id */ "item_id", 1,
                                /* 产品报价 */ "quantity", price, /* 有效标记 */"flag", 0, /* 产品税率 */ "amount", tax,
                                /* 价格目录编号 */ "optional", this.getId(), /* 所有者id */ "drcr", ownerId,
                                /* 有效标记 */"flag", 0, /* 价格目录说明 */"description", pct.getName())
                            ); // apply
                        } else { // 
                            
                            println(FT("$0-$1",owner.getName(),this.getName()),"add",quoteItem, "已经存在");
                        } // if
                    }); // 价格目录制作
                } // if
                
                return this;
            }
            
            /**
             * 从价格表目录中删除产品
             *
             * @param quoteItems [产品列表]
             * @return Catalog 本身
             */
            public Catalog remove(final Iterable<Product> quoteItems) {
                final var catalogId = this.getId();
                final var ids = StreamSupport.stream(quoteItems.spliterator(), false).map(e->e.getId()+"").collect(Collectors.joining(","));
                // entity_id 对应产品id, item_id 对应货币id
                final var sql  = FT("delete from t_entity_item_rel where optional=$0 and name='t_product/t_money' and entity_id in ($1)",catalogId,ids);
                
                dataApp.withTransaction(sess -> {
                    sess.sql2execute(sql);
                });
                
                return this;
            }
            
            /**
             * 从价格表目录中更新产品
             *
             * @param quoteItems 产品报价 [{product,price,tax}]，对于未出现在先前产品项中的新产品给予添加
             * @return Catalog 本身
             */
            public Catalog update(final Iterable<IRecord> quoteItems) {
                
                final var catalogId = this.getId();
                final var sql = FT("select * from t_entity_Item_rel where name='t_fortune/t_catalog' and item_id=$0",catalogId); // 检查catalog是否存在
                final var catalog_opt = dataApp.sql2maybe(sql);
                if (catalog_opt.isPresent()) {
                    final Integer ownerId = catalog_opt.map(e->e.i4("entity_id")).get(); // entity_id  对应owner id
                    dataApp.withTransaction(sess -> {
                        for (final var quoteItem : quoteItems) {
                            
                            final var pct = quoteItem.get("product", Product.class);
                            if (pct == null) continue;
                            
                            final var price = quoteItem.dbl("price", pct.getPrice().getAmount());
                            final var tax = quoteItem.dbl("tax", 0.17);
                            final var item_opt = dataApp.rel2maybe_rec_f("t_product", "t_money").apply(REC(
                                "entity_id", pct.getId(), "item_id", 1, "optional", this.getId())  // item_id 对应于 moneyid
                            ); // 检查价格目录的产品项目是否存在,optional为价格目录编号,entity_id 对应产品id
                            if(item_opt.isPresent()) {
                                final var upt_time = LocalDateTime.now();
                                final var where = FT("name ='t_product/t_money' and optional=$0 and entity_id=$1",this.getId(),pct.getId());
                                final var upt_sql  = FT("update t_entity_Item_rel set quantity=$0,amount=$1,update_time='$2' where $3",
                                        price,tax,upt_time,where);// quantity 对应 price, amount 对应 tax
                                //println(upt_sql);
                                sess.sqlexecute(upt_sql);
                            } else {// 新创建
                                final var create_time = LocalDateTime.now();
                                final var rec = REC(
                                    "path", "D:/sliced/files/products/valuables/gold.jpg","name","t_product/t_money", // 产品项表头
                                    /* 产品id */ "entity_id", pct.getId(), /* 货币id */ "item_id", 1,
                                    /* 产品报价 */ "quantity", price, /* 有效标记 */"flag", 0, /* 产品税率 */ "amount", tax,
                                    /* 价格目录编号 */ "optional", this.getId(), /* 所有者id */ "drcr", ownerId,
                                    /* 有效标记 */"flag", 0, /* 价格目录说明 */"description", pct.getName(),
                                    "create_time",create_time
                                );
                               
                                final var ins_sql = SQL.of("t_entity_item_rel", rec).insert();
                                //println(ins_sql);
                                sess.sql2execute(ins_sql);
                            }// if
                        }// for
                    }); // withTransaction
                } else {
                    System.err.print(FT("catalog $0 不存在无法更新",this.getId()));
                } // if
                
                return this;
            }
            
            /**
             * 获取产品报价
             * 
             * @return 产品报价
             */
            public List<PriceQuote> getPriceQuotes(){
                final var sql = FT("select * from t_entity_item_rel where name='t_product/t_money' and optional='$0'", this.getId());
                final var _sql = FT( "select a.entity_id id, b.name, b.price baseprice, a.quantity price, b.money,a.amount ratio, a.description "
                    + "from ($0) a inner join t_product b on a.entity_id = b.id", sql);
                // println(_sql);
                return dataApp.sqlqueryS(_sql).map(e -> {
                    final var pct = new Product(e);
                    // println(e);
                    final var price = financeApp.rmb(e.dbl("price"));
                    final var priceQuote = new PriceQuote(pct, price);
                    return priceQuote;
                }).collect(Collectors.toList());
            }
            
            /**
             * 属主id
             * @return 属主id
             */
            public Integer getOwnerId() {
                
                final var ownerid = this.attributes == null ? null : this.attributes.i4("ownerid");
                
                return ownerid;
            }

            @Override
            public Iterator<PriceQuote> iterator() {
                return this.getPriceQuotes().iterator();
            }
            
            /**
             * 返回产品价格流
             * 
             * @return 产品价格流
             */
            public Stream<PriceQuote> stream() {
                return this.getPriceQuotes().stream();
            }
            
            /**
             * 返回产品价格流
             * 
             * @param <T>    结果元素类型
             * @param mapper 元素变化函数 priceQuote->t
             * @return 产品价格流
             */
            public <T> Stream<T> stream(final Function<PriceQuote, T> mapper) {
                return this.getPriceQuotes().stream().map(mapper);
            }
            
            /**
             * catalog 的产品变换
             * 
             * @param <U> 结果类型
             * @param collector 归集器
             * @return U 产品表的归集项目
             */
            public <U> U collect(Collector<? super PriceQuote, ?, U> collector) {
                return this.stream().collect(collector);
            }
        }

        /**
         * 企业：市场主体
         * 
         * @author Administrator
         *
         */
        public class Enterprise extends Entity<Enterprise>{
            
            public Enterprise(IRecord rec) {
                this(rec.i4("id"),rec.str("name"));
            }

            /**
             * 基本构造函数
             * @param id
             * @param name
             */
            public Enterprise(Integer id,String name) {
                this.id = id;
                this.name = name;
                this.store = new Store(Enterprise.this);
            }
            
            /**
             * 企业初始化
             * @return
             */
            public Enterprise initialize() {
                this.store.initialize();
                return this;
            }

            /**
             * 采购订单
             * 
             * @param supplier 供应商
             * @param items    产品列表
             * @return 采购订单
             */
            public PurchaseOrder purchase(final Enterprise supplier, final LineItem... items) {
                final var po = new PurchaseOrder(this, supplier);
                po.addAll(Arrays.asList(items));
                return po;
            }
            
            /**
             * 采购订单
             * 
             * @param supplier 供应商
             * @param items    产品列表
             * @return 采购订单
             */
            public PurchaseOrder purchase(final Enterprise supplier, final Iterable<LineItem>items) {
                return this.purchase(supplier, new XVec<>(items).toArray());
            }

            /**
             * 销售订单
             * 
             * @param buyer 客户
             * @param items 产品订单
             * @return 销售订单
             */
            public SalesOrder sale(final Enterprise buyer, final LineItem... items) {
                final var so = new SalesOrder(buyer, this);
                so.addAll(Arrays.asList(items));
                return so;
            }
            
            /**
             * 销售订单
             * 
             * @param buyer 客户
             * @param items 产品订单
             * @return 销售订单
             */
            public SalesOrder sale(final Enterprise buyer, final Iterable<LineItem>items) {
                return this.sale(buyer, new XVec<>(items).toArray());
            }
            
            /**
             * 
             * @return
             */
            public List<SalesOrder> getSaleOrders() {
                final var ar = new AtomicReference<List<SalesOrder>>();
                dataApp.withTransaction(sess->{
                    final var sql = FT("select * from t_contract where partB=$0",this.id);
                    final var ll = sess.sql2stream(sql).map(e->{
                        //println(e);
                        final var order = MyERPApp.this.getOrder(e.i4("id"));
                        //println(order);
                        return new SalesOrder(order);
                    }).collect(Collectors.toList());
                    ar.set(ll);
                });
                return ar.get();
            }
            
            /**
             * 
             * @return 采购订单
             */
            public List<PurchaseOrder> getPuchaseOrders() {
                final var ar = new AtomicReference<List<PurchaseOrder>>();
                dataApp.withTransaction(sess->{
                    final var sql = FT("select * from t_contract where partA=$0",this.id);
                    //println(sql);
                    final var ll = sess.sql2stream(sql).map(e->{
                        final var order = MyERPApp.this.getOrder(e.i4("id"));
                        return new PurchaseOrder(order);
                    }).collect(Collectors.toList());
                    ar.set(ll);
                });
                return ar.get();
            }

            public void setStore(Store store) {
                this.store = store;
            }
            
            public Store getStore() {
                return this.store;
            }
            
            /**
             * 
             * @param param 价格表参数：整数类型的catalogId 或是 IRecord类型的 产品目录的元数据 {name:名称,symbol:产品目录符号,description:产品目录说明,version:目录版本}
             * @param price_ratio 价格浮动比率 价格=产品基准价格*(1+price_ratio)
             * @param tax_ratio 税率
             * @param productPrices 产品价格清单
             * @return 产品目录
             */
            public Catalog buildCatalog(final Object param, double price_ratio, double tax_ratio,
                    final Iterable<BasePair<Product, Price>> productPrices) {
                Integer _catalogId = null;
                String _name = null;
                String _symbol = null;
                String _version = null;
                String _description = null;
                
                if (param instanceof Number) { // 价格表id
                    _catalogId = ((Number) param).intValue();
                } else if (param instanceof IRecord) { // 产品目录的元数据 {name:名称,symbol:产品目录符号,description:产品目录说明,version:目录版本}
                    final var uuid = UUID.randomUUID();
                    final var rec = (IRecord) param;
                    _name = rec.str("name", "产品目录-" + uuid);
                    _symbol = rec.str("symbol", "CATALOG-" + uuid);
                    _description = rec.str("description", FT("$0-$1", this.getName(), _name));
                    _version = rec.str("version", "1.1");
                    
                    final var sql = FT("select * from t_catalog where (name='$0' or symbol='$1') and ownerid=$2 ", _name, _symbol, this.getId());
                    final var opt = dataApp.sql2maybe(sql);
                    _catalogId = opt.map(e->e.i4("id")).orElse(null); // 提取产品目录
                
                } else {
                    println("参数非法");
                    return null;
                }
                
                final var catalogId = _catalogId;
                final var catalog_name = _name;
                final var catalog_symbol = _symbol;
                final var catalog_version = _version;
                final var catalog_description = _description;
                final var quoteItems = StreamSupport.stream(productPrices.spliterator(),false).map( bp -> REC(
                        "product", bp._1(), 
                        "price", bp._2().mul(1 + price_ratio).getAmount(), 
                        "tax", tax_ratio)
                );
                final var ar = new AtomicReference<Catalog>();
                
                // 制作产品目录
                dataApp.withTransaction(sess -> {
                    final Optional<IRecord> catalog_opt = catalogId == null 
                            ? Optional.empty()
                            : sess.sql2maybe(FT("select * from t_catalog where id=$0 and ownerid = $1 ", catalogId, this.getId()));
                    final Function<IRecord, Catalog> catalog_mutator = rec -> {
                        final var _catalog = rec.mutate(Catalog::new);
                        _catalog.setAttributes(rec);
                        return _catalog;
                    };
                    Catalog catalog = catalog_opt.map(catalog_mutator).orElse(null); // 产品目录
                    
                    if (catalog_opt.isEmpty()) {
                        final var create_time = LocalDateTime.now();
                        final var catalog_attrs = REC("name", catalog_name, "symbol", catalog_symbol, "path",
                                "D:/sliced/files/products/valuables/gold.jpg", "version", catalog_version, "flag", 0,
                                "ownerid", this.getId(), "description", catalog_description, "price", 1, "create_time", create_time); // 价格目录信息
                        final var ins_catalog_sql = SQL.of("t_catalog", catalog_attrs).insert(); // 数据插入 sql
                        final var catalog_id = sess.sql2execute2int(ins_catalog_sql); // 获得生成的 项目id
                        catalog = catalog_attrs.derive("id", catalog_id).mutate(catalog_mutator); // 变换成 产品目录对象
                    } // if catalog_opt
                    
                    final var sql = FT("select * from t_entity_item_rel where name='t_fortune/t_catalog' and entity_id=$0 and item_id=$1", this.getId(), catalog.getId());
                    final var opt = sess.sql2maybe(sql);
                    if (opt.isEmpty()) { // 企业添加产品目录
                        final var optional = this.getName() + "-产品目录-" + catalog.getName();
                        final var create_time = LocalDateTime.now(); // 创建时间
                        final var entity_item_rel = REC("name", "t_fortune/t_catalog", "path",
                                "D:/sliced/files/products/valuables/gold.jpg", "entity_id", this.getId(), "item_id",
                                catalog.getId(), "optional", optional, // 基本关系
                                "drcr", -1, "quantity", -1, "amount", -1, "flag", 0, "description", "产品目录", "create_time", create_time);
                        // println(entity_item_rel);
                        sess.sql2execute2int(SQL.of("t_entity_item_rel", entity_item_rel).insert());
                    } else { // if
                        println(FT("$0-$1 的catalog已经存在",this.getId(),catalog.getId()));
                    }
                    
                    catalog.add(quoteItems.collect(Collectors.toList()));
                    ar.set(catalog); // 设置产品目录
                }); // withTransaction
                
                return ar.get();
            }

            /**
             * 获取产品目录
             * 
             * @return
             */
            public List<Catalog> getCatalogs() {
                final var sql = FT("select * from t_entity_item_rel where name='t_fortune/t_catalog' and entity_id=$0", this.id);
                final var _sql = FT("select b.id,b.name from ($0) a inner join t_catalog b on a.item_id = b.id", sql);
                return dataApp.sqlqueryS(_sql).map(Catalog::new).collect(Collectors.toList());
            }
            
            /**
             * 获取产品目录
             * 
             * @return
             */
            public Optional<Catalog> getCatalog(final int catalog_id) {
                final var sql = FT("select * from t_entity_item_rel where name='t_fortune/t_catalog' and entity_id=$0", this.id);
                final var _sql = FT("select b.id,b.name from ($0) a inner join t_catalog b on a.item_id = b.id where b.id = $1", sql,catalog_id);
                // println(_sql);
                return dataApp.sql2maybe(_sql).map(Catalog::new);
            }
            
            /**
             * 获取 产品目录
             * 
             * @param catalogName 产品目录名
             * @return 产品目录名
             */
            public Optional<Catalog> getCatalog(final String catalogName) {
                final var sql = FT("select * from t_entity_item_rel where name='t_fortune/t_catalog' and entity_id=$0", this.id);
                final var _sql = FT("select b.id,b.name from ($0) a inner join t_catalog b on a.item_id = b.id where b.name = '$1'", sql,catalogName);
                // println(_sql);
                return dataApp.sql2maybe(_sql).map(Catalog::new);
            }
            
            /**
             * 获取 产品目录
             * 
             * @param catalogName 产品目录名
             * @return 产品目录名
             */
            public Catalog catalogOf(final String catalogName) {
                final var sql = FT("select * from t_entity_item_rel where name='t_fortune/t_catalog' and entity_id=$0", this.id);
                final var _sql = FT("select b.id,b.name from ($0) a inner join t_catalog b on a.item_id = b.id where b.name = '$1'", sql,catalogName);
                // println(_sql);
                return dataApp.sql2maybe(_sql).map(Catalog::new).orElse(null);
            }
            
            /**
             * 获取 产品报价清单
             * 
             * @param catalogName 产品目录名
             * @return 产品报价清单
             */
            public XVec<PriceQuote> catalogX(final String catalogName) {
                return this.catalogOf(catalogName).collect(XVec.xveclc());
            }
            
            /**
             * getDefaultCatalog 的别名 <br>
             * 获取默认产品
             * 
             * @return 产品目录
             */
            public Catalog getCatalog() {
                return this.getDefaultCatalog();
            }
            
            /**
             * 获取默认产品
             * 
             * @return 产品目录
             */
            public Catalog getDefaultCatalog() {
                if(this.defaultCatalog==null) {
                    final var catalogs = this.getCatalogs();
                    if(catalogs.size()>0) { // 设定第一号的catalog为默认产品目录
                        this.defaultCatalog = catalogs.get(0);
                    }// if
                }// if
                return this.defaultCatalog;
            }
            
            /**
             * 设置 产品目录
             * @param catalog 产品目录
             * @return 企业对象本身
             */
            public Enterprise setDefaultCatalog(final Catalog catalog) {
                this.defaultCatalog = catalog;
                return this;
            }
            /**
             * 更新企业的信用额度
             * 
             * @param partB 企业对象
             * @param updator 更新函数 r:先前值,新纪录为null->{ return 更新值;};
             */
            public void updateCredit(final Enterprise partB, final Function<Double, Double> updator) {
                this.updateCredit(partB.getId(), updator);
            }
            
            /**
             * 更新企业的信用额度
             * 
             * @param partB 企业B
             * @param updator 更新函数 r:先前值,新纪录为null->{ return 更新值;};
             */
            public void updateCredit(final Integer partB, final Function<Double, Double> updator) {
                final var partA = this.getId(); // 主键id
                final var name = "t_fortune/t_fortune"; // 企业对企业
                final var optional = "CREDIT_AMOUNT"; // 信用额度
               
                dataApp.withTransaction(sess -> {
                    final var select_sql = FT("select * from t_entity_item_rel where name='$0' and optional='$1' and entity_id='$2'", name, optional, partA);
                    final var rec_opt = sess.sql2maybe(select_sql); // 数据检索
                    final var pre_credit_amount  = rec_opt.map(e->e.dbl("amount")).orElse(null); // 先前的数值
                    final var credit_amount = updator.apply( pre_credit_amount ); // 新的信用额度
                    
                    if (rec_opt.isEmpty()) { // 数据不存在则插入
                        final var create_time = LocalDateTime.now(); // 创建时间
                        final var entity_item_rel = REC( // 关系分录
                                "name", name, "path", "D:/sliced/files/products/valuables/gold.jpg", // 明细记录
                                "entity_id", partA, "item_id", partB, "optional", optional, // 关系数据，optional 表示信用额度属性
                                "drcr", -1, "quantity", -1, "amount", credit_amount, "flag", 0, "description", "信用额度表", "create_time", create_time // 借贷分录
                        ); // 关系分录
                        final var insert_sql = SQL.of("t_entity_item_rel", entity_item_rel).insert(); // 插入语句
                        sess.sqlexecute(insert_sql); // 数据插入
                    } else { // 数值存在则更新
                        final var upt_time = LocalDateTime.now(); // 更新时间
                        final var id = rec_opt.map(e -> e.i4("id")).get(); // 记录主键
                        final var update_sql = FT( "update t_entity_item_rel set quantity=$1, amount=$2, update_time='$3' where id=$0", id,
                                pre_credit_amount, credit_amount, upt_time ); // 更新语句
                        sess.sqlexecute(update_sql); // 数据更新
                    } // if rec_opt.isPresent()
                }); // withTransaction
            }
            
            /**
             * 获取一个企业的信用额度
             * 
             * @param partB 客户企业
             * @return 信用额度
             */
            public Double getCredit2(final Enterprise partB) {
                return this.getCredit(partB.getId()).orElse(0d);
            }

            /**
             * 获取一个企业的信用额度
             * 
             * @param partB 客户企业
             * @return 信用额度
             */
            public Double getCredit2(final Integer partB) {
                return this.getCredit(partB).orElse(0d);
            }

            /**
             * 获取一个企业的信用额度
             * 
             * @param partB 客户企业
             * @return 信用额度
             */
            public Optional<Double> getCredit(final Enterprise partB) {
                return this.getCredit(partB.getId());
            }

            /**
             * 获取一个企业的信用额度
             * 
             * @param partB 客户企业
             * @return 信用额度
             */
            public Optional<Double> getCredit(final Integer partB) {
                final var partA = this.getId();
                final var name = "t_fortune/t_fortune"; // 企业对企业
                final var optional = "CREDIT_AMOUNT"; // 信用额度标记
                final var select_sql = FT(
                        "select * from t_entity_item_rel where name='$0' and optional='$1' and entity_id='$2' and item_id='$3'",
                        name, optional, partA, partB);
                return dataApp.sql2maybe(select_sql).map(e -> e.dbl("amount"));
            }
            
            /**
             * 获得库房
             * @return
             */
            public List<WareHouse> getHouses(){
                return this.store.houses;
            }
            
            /**
             * @return the address
             */
            public String getAddress() {
                return address;
            }

            /**
             * @return the telephone
             */
            public String getTelephone() {
                return telephone;
            }

            /**
             * @param address the address to set
             */
            public void setAddress(String address) {
                this.address = address;
            }

            /**
             * @param telephone the telephone to set
             */
            public void setTelephone(String telephone) {
                this.telephone = telephone;
            }
            
            /**
             * 默认库房
             * @return
             */
            public WareHouse getDefualtHouse() {
                return this.store.getDefaultWareHouse();
            }
            
            /**
             * 获取企业的银行的账户余额,合并多个银行 为一行科目 即 银行合列
             * 
             * @param banks 银行列表
             * @return DFrame
             */
            public DFrame trialBalance(final Bank ... banks) {
                return this.trialBalance(true,banks); // 银行合列
            }
            
            /**
             * 获取企业的银行的账户
             * 
             * @param b 是否合并多个银行 为一行科目, false 银行分列, true 银行合列
             * @param banks 银行列表
             * @return DFrame
             */
            public DFrame trialBalance(final boolean b,final Bank ... banks) {
                final var bankIds = Stream.of(banks).map(Bank::getId).map(e->"'"+e+"'").collect(Collectors.joining(","));
                final var flds = "ref bankid, entity_id tx, drcr, item_id acctid, if(drcr=0,1,-1)*amount amount ";
                final var sql0 = FT("select $0 from t_entity_item_rel where name='t_transaction/t_coa' and ref in ($1) and optional='$2'",flds,bankIds,this.getId());
                final var sql1 = FT("select $0 bankid, acctid, sum(amount) amount from ($1) t1 group by $2", b ? bankIds.replace("','", ",") : "bankid", sql0, !b ? "bankid,acctid" : "acctid");
                final var sql2 = FT("select bankid, acctid, c.name acct, amount from ($0) t3 inner join t_coa c on t3.acctid = c.code ",sql1);
                final var sql = sql2;
                // println(sql);
                return dataApp.sqlqueryS(sql).collect(DFrame.dfmclc);
            }
            
            /**
             * 会计记账
             * 
             * @param recs 复式记账
             * @return FinanceEntity
             */
            @SuppressWarnings("unchecked")
            public FinanceEntity<Enterprise> withAcctTransaction(final IRecord... recs) {
                
                final var finance_entity = this.getFinanceEntity();
                final var acct_txs = Stream.of(recs).map(e -> BasePair.bp(finance_entity, e)).toArray(BasePair[]::new);

                MyERPApp.this.withAcctTransaction(acct_txs);

                return finance_entity;
            }
            
            /**
             * 
             * @return
             */
            public FinanceEntity<Enterprise> getFinanceEntity(){
                return MyERPApp.this.getBank(this.getId(), false);
            }

            private String telephone;
            private String address;
            private Store store;
            private Catalog defaultCatalog;
        }
        
        /**
         * 
         * @author gbench
         *
         * @param <T>
         */
        public class FinanceEntity<T> extends Entity<T> {
            
            /**
             * 金融主体
             * @param entity
             */
            public FinanceEntity(final Entity<T> entity){
                this.id = entity.getId();
                this.name = entity.getName();
                this.attributes = entity.getAttributes();
            }
            
            /**
             * 会计报表
             * @return DFrame
             */
            public DFrame trialBalance() {
                final var flds = "entity_id transcode,drcr,item_id acctid,optional,description acct,if(drcr=0,1,-1)*amount total";
                final var s1 = FT("select $0 from t_entity_item_rel where name='t_transaction/t_coa' and ref='$1'",flds,this.getId());
                final var s2 = FT("select acctid , acct, optional, sum(total) balance, count(*) cnt from ($0) t1 group by acctid,optional ",s1); // acctid(科目)-optional(子科目) 的分类统计
                final var s3 = FT("select if(t2.balance>=0,'借','贷') drcr,t2.* from ($0) t2 order by drcr",s2);
                // println(s2);
                return dataApp.sqlqueryS(s3).collect(DFrame.dfmclc);
            }
            
            /**
             * 事务函数
             * trans 交易名称
             * @param recs 会计记账信息 (name,create_time,entries:[(drcr,acctid,amount)]
             */
            public void withAcctTransaction(final IRecord ... recs) {
                @SuppressWarnings("unchecked")
                final BasePair<FinanceEntity<Enterprise>,IRecord>[] acct_txs = Stream.of(recs).map(e->bp(this, e)).toArray(BasePair[]::new);
                MyERPApp.this.withAcctTransaction(acct_txs);
            }
        }
        
        /**
         * 银行 任何企业 都是一个银行对象<br>
         * 
         * 银行就是 一个 拥有记账功能的 财务实体。
         * 一个银行对象 就是一个 拥有采用记账功能的企业, 没有什么特殊性 <br>
         * 一般我们会把一个企业 转换成一个 银行对象 来对该企业进行 财务核算。
         * @author gbench
         *
         */
        public class Bank extends FinanceEntity<Enterprise>{

            /**
             *
             * @param entity
             */
            public Bank(final Entity<Enterprise> entity) {
                super(entity);
            }
            
            /**
             * 存款
             * dr 库存现金
             * cr 吸收存款
             */
            public void deposit(final FinanceEntity<?> entity) {
                
            }
            
            /**
             * 取款：
             * dr 吸收存款
             * cr 库存现金
             */
            public void withdraw(final FinanceEntity<?>  entity) {
                
            }
            
        }

        /**
         * Store 是一个House的分组
         * 
         * @author Administrator
         *
         */
        public class Store extends Entity<Store> {
        
            Store(Enterprise owner) {
                owner.setStore(this);
                this.owner = owner;
                this.name = this.owner.getName();
            }
            
            /**
             * 初始化仓库
             */
            public void initialize() {
                final var items = dataApp.relqueryR("t_fortune", "t_house", this.owner.id);
                final var store = owner.store;
                items.forEach(r -> {
                    dataApp.sql2maybe(FT("select * from t_house where id=$0",r.str("item_id")))
                    .map(rec->{
                        final var house = store.new WareHouse(rec);
                        store.addWareHouse(house);
                        return house;
                    });
                }); // forEach
            }
        
            /**
             * 添加库房
             * 
             * @param house
             */
            public void addWareHouse(WareHouse house) {
                this.houses.add(house);
            }
        
            /**
             * 库房
             * 
             * @author Administrator
             *
             */
            class WareHouse extends Entity<WareHouse> {

                /**
                 *
                 * @param rec
                 */
                WareHouse(final IRecord rec){
                    this(rec.i4("id"),rec.str("name"),rec.str("address"));
                    //println(rec);
                }
        
                /**
                 * 
                 * @param name
                 */
                WareHouse(final Integer id, String name,String address) {
                    this.id = id;
                    this.name = name;
                    this.address = address;
                }
                
                /**
                 * 试算平衡
                 * @return
                 */
                public DFrame trialBalance() {
                    final var sql = FT("SELECT item_id id,"
                            + " sum(if(drcr=0,1,-1) * quantity) quantity, "
                            + " sum(if(drcr=0,1,0) * quantity) dr,"
                            + " sum(if(drcr=0,0,1) * quantity) cr "
                            + " FROM tbl$ "
                            + " where entity_id=$0 group by item_id",this.getId());
                    final var _sql = FT("select t.id,p.name,t.dr,t.cr,t.quantity"
                            + " from ($0) t inner join t_product p on t.id = p.id"
                            + " order by quantity desc",sql);
                    final var dd = dataApp.rel2df_sql_f("t_house", "t_product")
                            .apply(_sql);
                    return dd;
                }
                
                /**
                 * 库房日记账
                 * @return
                 */
                public DFrame houseJounal(Enterprise enterprise) {
                    final var order_tbl = FT("select if(partA=$0,1,-1) drcr,a.* from t_contract a where partA=$0 or partB=$0",enterprise.getId()); // 订单表
                    final var t = FT("select tbl.id, o.partA, o.partB, o.drcr o_drcr,tbl.drcr i_drcr, o.id house_id, tbl.item_id, tbl.quantity,tbl.optional order_id, tbl.create_time "
                            + "from tbl$ inner join ($0) o on o.id = tbl.optional",order_tbl); // o.id = tbl.optional 表示:出库记录 中的 optional 对应于 订单表t_contract的id
                    // println(t);
                    return dataApp.rel2df_sql_f("t_house", "t_product").apply(t);
                }
                
                /**
                 * 获取地址
                 * @return
                 */
                public String getAddress() {
                    return this.address;
                }
                
                private String address;
            }
        
            /**
             * 存货,存货是 绑定了产品的与库房的产品。
             * 
             * @author Administrator
             *
             */
            class Stock {
                Product product;
                WareHouse house;
                Double quantity;
            }
        
            /**
             * 
             * @return
             */
            public WareHouse getDefaultWareHouse() {
                if (this.defaultWareHouse == null && this.houses.size() > 0) {
                    this.defaultWareHouse = this.houses.get(0);
                }
            
                return this.defaultWareHouse;
            }

            /**
             * 默认库房
             * @param house
             */
            public void setDefaultHouse(WareHouse house) {
                this.defaultWareHouse = house;
            }
            
            /**
             * 试算平衡表
             * @return
             */
            public DFrame trialBalance() {
                
                return this.trialBalance(this.getDefaultWareHouse());
            }
            
            /**
             * 试算平衡表
             * @return
             */
            public DFrame trialBalance(WareHouse house) {
                if(null==house) {
                    println("库房为空不予试算！");
                    return null;
                }
                println(FT("$0-$1-试算平衡",this.getName(),house.getName()));
                return house.trialBalance();
            }
        
            /**
             * 入库: 在 t_entity_item_rel 表中插入一条数据。 name:t_house/t_product
             *
             * @param product
             * @param quantity
             * @param orderId
             * @param house
             */
            public void inbound(Product product, Double quantity,Integer orderId,WareHouse house) {
                if(house==null) {
                    println("库房为空,无法出产品入库");
                    return;
                }
                
                // 此处 把 amount 位置用于存储 入库的 owner id
                dataApp.rel2insert("t_house" /*entity_tbl*/, "t_product" /*item_tbl*/, house.getId() /*entity_id*/, product.getId() /*item_id*/, 
                        0 /*drcr*/, quantity /*quantity*/, 0 /*flag*/, 0 /*amount*/, this.getOwner().getId() /*ref*/, orderId /*optional*/, 
                        FT("$0#$1[入库]$2",this.getOwner().getId(),this.getOwner().getName(),product.getName()) /*description*/);
            }
        
            /**
             * 入库
             * 
             * @param product 产品入库
             * @param quantity
             * @param orderId
             */
            public void inbound(Product product, Double quantity,Integer orderId) {
                this.inbound(product, quantity,orderId, this.getDefaultWareHouse());
            }
            
            /**
             * 产品入库
             * @param inbound 入库单
             */
            public void inbound(InboundBill inbound) {
                final var id = inbound.getOrder().getId();
                inbound.getLineItems().forEach(e->{
                    this.inbound(e.product, e.quantity,id);
                });
            }
        
            /**
             * 出库
             * 
             * @param product 出库产品
             * @param quantity 出库数量
             * @param orderId 销售订单
             * @param house 出库库房
             */
            public void outbound(final Product product, final Double quantity, final Integer orderId,final WareHouse house) {
                if(house==null) {
                    println("库房为空,无法出产品出库");
                    return;
                }
                
                // 此处 把 amount 位置用于存储 入库的 owner id
                dataApp.rel2insert("t_house" /*entity_tbl*/, "t_product" /*entity_tbl*/, house.getId() /*entity_id*/, product.getId() /*item_id*/, 
                    1 /*drcr*/, quantity /*quantity*/,0 /*flag*/, 0 /*amount*/, this.getOwner().getId() /*ref*/, orderId /*optional*/, 
                    FT("$0#$1[出库]$2",this.getOwner().getId(),this.getOwner().getName(),product.getName()) /*description*/);
            }
        
            /**
             * 出库
             * 
             * @param product 产品对象
             * @param amount 入库数量
             * @param orderId 销售订单编号
             */
            public void outbound(final Product product, final Double amount,Integer orderId) {
                this.outbound(product, amount, orderId,this.getDefaultWareHouse());
            }
            
            /**
             * 产品出库
             * @param outbound 出库单
             */
            public void outbound(OutboundBill outbound) {
                final var id = outbound.getOrder().getId();
                outbound.getLineItems().forEach(e->{
                    this.outbound(e.product, e.quantity,id);
                });
            }
            
            /**
             * 添加房屋
             * @param house
             */
            public void addHouse(final WareHouse house) {
                final var entityTbl = "t_fortune";
                final var itemTbl = "t_house";
                println(house);
                final var opt = dataApp.rel2maybe_rec_f(entityTbl, itemTbl).apply(REC("id", house.id));
                if (opt.isEmpty()) {
                    final var rec = REC("item_id", house.id, "entity_id", this.owner.getId(), "optional", this.owner.getName()+"-库房-"+house.getName(), "drcr", -1,"flag",0,
                            "quantity", -1, "amount", -1, "description", house.getName(), "create_time", LocalDateTime.now());
                    dataApp.rel2insert_rec_f(entityTbl, itemTbl).apply(rec);
                } else {
                    println("house 已经存在", house, opt);
                }
            }
            
            /**
             * 
             * @return
             */
            public Enterprise getOwner() {
                return this.owner;
            }
        
            private Enterprise owner;
            private WareHouse defaultWareHouse;
            private List<WareHouse> houses = new LinkedList<WareHouse>();
        }

        /**
         * 合约对象<br>
         * 一个合约对象 表示这是一个 双方就谋事 而达成的 一种约定,是一种约定关系
         * 
         * @author gbench
         *
         */
        public class Contract extends Entity<Contract>{
            
            /**
             * 默认构造
             */
            protected Contract() {

            }

            /**
             * 合约对象
             * 
             * @param partA 甲方
             * @param partB 乙方
             */
            public Contract(final Enterprise partA, final Enterprise partB) {
                this(null, partA, partB);
            }

            /**
             * 合约对象
             * 
             * @param id    合同id
             * @param partA 甲方
             * @param partB 乙方
             */
            public Contract(final Integer id, final Enterprise partA, final Enterprise partB) {
                this.id = id;
                this.partA = partA;
                this.partB = partB;
                this.status = new Status(0);
            }
            
            /**
             * 数据格式化
             */
            public String toString() {
                return FT("#$0-$1-$2", this.id == null ? "NEW" : this.id, this.name, this.createTime);
            }

            /**
             * @return the partA
             */
            public Enterprise getPartA() {
                return partA;
            }

            /**
             * @return the partB
             */
            public Enterprise getPartB() {
                return partB;
            }

            /**
             * @return the status
             */
            public Status getStatus() {
                return status;
            }

            /**
             * @return the createTime
             */
            public LocalDateTime getCreateTime() {
                return createTime;
            }

            /**
             * @param partA the partA to set
             */
            public void setPartA(Enterprise partA) {
                this.partA = partA;
            }

            /**
             * @param partB the partB to set
             */
            public void setPartB(Enterprise partB) {
                this.partB = partB;
            }

            /**
             * @param status the status to set
             */
            public void setStatus(Status status) {
                this.status = status;
            }

            /**
             * @param createTime the createTime to set
             */
            public void setCreateTime(LocalDateTime createTime) {
                this.createTime = createTime;
            }
            
            /**
             * 甲方
             */
            private Enterprise partA;

            /**
             * 乙方
             */
            private Enterprise partB;

            /**
             * 合同状态
             */
            private Status status = new Status(0);

            /**
             * 创建时间
             */
            private LocalDateTime createTime = LocalDateTime.now();
        }

        /**
         * 订单对象
         * 
         * @author Administrator
         *
         */
        public class Order<S> extends Contract implements Iterable<LineItem>{
            
            /**
             * 订单对象
             * 
             * @param contract 合同对象
             */
            public Order(final Contract contract) {
                super(contract.id, contract.partA, contract.partB);
                this.name = contract.name;
            }
            
            /**
             * 
             * @param partA
             * @param partB
             */
            public Order(final Enterprise partA, final Enterprise partB) {
                super(partA, partB);
            }
            
            /**
             * 数据持久化
             * 
             * @return
             */
            public Order<S> persist() {
                // 合同数据
                final var amount = this.lineItems.stream().filter(e->e.getTotal()!=null)
                    .collect(Collectors.summarizingDouble(e->e.getTotal().getAmount()))
                    .getSum();
                final var rec = REC("partA", this.getPartA().getId(), "partB", this.getPartB().getId(), 
                    "name", this.name, "path","D:/sliced/files/products/valuables/gold.jpg", 
                    "amount",amount,"flag", this.getStatus().getId(), "create_time",this.getCreateTime());
                final var dtfmt = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
                
                dataApp.withTransaction(sess -> {
                    if (this.getId() == null) { // 数据尚未持久化
                        rec.set("name", FT("$0-$1",rec.str("name"),now().format(dtfmt)));// 合同名加入时间后缀
                        final var sql = SQL.of("t_contract", rec).insert();
                        //println(sql);
                        final var entity_id = this.id = sess.sql2execute2int(sql); // 数据插入
                        final var pattern = FT("insert into t_entity_item_rel"
                            + "(name,path,entity_id,item_id,quantity,amount,drcr,optional,description,flag,create_time) values "
                            + "${foreach e in entries ('t_contract/t_product','D:/sliced/files/products/valuables/gold.jpg',"
                            +    "e.entityId,e.itemId,e.quantity,e.amount,0,e.optional,e.description,0,'$0')}",LocalDateTime.now());
                    
                        final var entries = this.lineItems.stream().map(e -> REC("entityId", entity_id, "itemId", e.product.getId(), "quantity",
                            e.quantity, "amount", e.getTotal().getAmount(), "optional", e.getUnitPrice().getAmount(),
                            "description", FT("$0[$1]", e.product.getName(), e.product.attr("money", "-"))) // REC
                        ).collect(Collectors.toList());
                        
                        if (this.lineItems != null) {
                            sess.sqlexecute(nsql(pattern,REC("entries",entries)));
                        }// if
                       
                    } else { // 数据已经持久化
                        final var r = REC("id", this.getId()).derive(rec);
                        final var sql = SQL.of("t_contract", r).update2();
                        //println(sql);
                        sess.sql2execute2int(sql); // 数据插入
                    } // if
                }); // withTransaction
            
                return this;
            }

            /**
             * 获取行项目
             * 
             * @param b 是否强制刷新,false 读取缓存,true 读取数据库
             * @return 获取行项目
             */
            public List<LineItem> getLineItems(final boolean b) {
                if(b) {
                    final var sql = FT("select b.id,b.name,b.price,b.money,a.quantity,a.amount "
                        + "from (select * from tbl$ where entity_id=$0) a inner join t_product b on a.item_id=b.id", this.id);
                    
                    this.lineItems = dataApp.rel2df_sql_f("t_contract", "t_product").apply(sql).rowsS()
                        .map(e -> new LineItem(new Product(e), e.dbl("quantity"))).collect(Collectors.toList());
                }// if
                
                return this.lineItems;
            }

            /**
             * 行项目
             * @return
             */
            public List<LineItem> getLineItems() {
                return this.getLineItems(false);
            }

            /**
             * 
             * @param product 产品对象
             * @param n 产品数量
             */
            public void addItem(final Product product, final Number n) {
                final var item = new LineItem(product, n.doubleValue());
                this.lineItems.add(item);
            }
            
            /**
             * 加入所有lineItems
             * @param lineItems
             */
            public void addAll(final List<LineItem> lineItems) {
                this.lineItems.addAll(lineItems);
            }
            
            /**
             * 订单总额
             * 
             * @return 获取总额
             */
            public Double getTotal() {
                return this.stream().collect(Collectors.summarizingDouble(e->e.unitPrice.amount * e.getQuantity())).getSum();
            }
            
            /**
             * 订单的刷新
             */
            @SuppressWarnings("unchecked")
            public S refresh() {
                this.lineItems.clear();
                this.lineItems.addAll(this.getLineItems(true));
                return (S) this;
            }
            
            /**
             * 迭代器
             */
            @Override
            public Iterator<LineItem> iterator() {
                return lineItems.iterator();
            }
            
            /**
             * 数据流
             * @return LineItem 的流
             */
            public Stream<LineItem> stream(){
                return StreamSupport.stream(this.spliterator(), false);
            }
            
            /**
             * 归集器
             * 
             * @param <U>       归并结果
             * @param mapper    行项目映射器 lineitem->u
             * @param collector 归集器
             * @return U 类型的结果
             */
            public <T, U> U collect(final Function<LineItem, T> mapper, final Collector<T, ?, U> collector) {
                return this.stream().map(mapper).collect(collector);
            }

            /**
             * 归集器
             * 
             * @param <U>       归并结果
             * @param collector 归集器
             * @return U 类型的结果
             */
            public <U> U collect(final Collector<LineItem, ?, U> collector) {
                return this.collect(e -> e, collector);
            }

            /**
             * 订单的格式化输出
             */
            public String toString() {
                if (this.lineItems.size() < 1) this.refresh(); // 刷新行项目
                final var details = this.lineItems.stream().map(e->e.product.getName()+"/"+e.quantity).collect(Collectors.joining(","));
                return FT("<$0[$1]>",super.toString(),details.length()>50?details.substring(0,50)+"...":details);
            }

            private List<LineItem> lineItems = new LinkedList<LineItem>();

        }

        /**
         * 采购订单
         * 
         * @author gbench
         *
         */
        public class PurchaseOrder extends Order<PurchaseOrder> {
            
            /**
             * 采购订单
             * 
             * @param order 订单
             */
            public PurchaseOrder(final Order<?> order) {
                super(order);
            }

            /**
             * 基本构造函数
             * 
             * @param partA 甲方
             * @param partB 乙方
             */
            public PurchaseOrder(final Enterprise partA, final Enterprise partB) {
                super(partA, partB);
                this.name = FT("PO-$0-$1", partA.id, partB.id);
            }

            /**
             * 
             * @return
             */
            public Enterprise getCustomer() {
                return this.getPartB();
            }

            /**
             * 获取订单属主
             * 
             * @return 订单属主
             */
            public Enterprise getOwner() {
                return this.getPartA();
            }

        }

        /**
         * 销售订单
         * @author gbench
         *
         */
        public class SalesOrder extends Order<SalesOrder> {
            
            public SalesOrder(final Order<?> order){
                super(order);
            }

            /**
             * 基本构造函数
             * @param partA
             * @param partB
             */
            public SalesOrder(Enterprise partA, Enterprise partB) {
                super(partA, partB);
                this.name = FT("SO-$0-$1",partA.id,partB.id);
            }
            
            /**
             * 
             * @return
             */
            public Enterprise getOwner() {
                return this.getPartB();
            }
        }

        /**
         * 结算单
         * 
         * @author Administrator
         *
         */
        public class Settlement {

        }
        
        /**
         * MyERPApp 的初始化
         */
        public void initialize() {
           financeApp.initialize(dataApp.sqlqueryS("select * from t_money where provider=0"));// 补充货币信息
        }

        /**
         * 企业id
         * @param id 企业id
         * @return Enterprise
         */
        public Enterprise getEnterprise(final int id) {
            return this.getEnterprise(id,false);
        }

        /**
         * 获取市场主体
         * 
         * @param id 企业id
         * @return Enterprise
         */
        public Enterprise getEnterprise(final int id, final boolean b) {
            return dataApp.sql2maybe(FT("select * from t_fortune where id = $0", id))
                    .map(e -> !b ? new Enterprise(e) : new Enterprise(e).initialize()).orElse(null);
        }
        
        /**
         * 获取一个 金融主体
         * @param enterprise 企业主体
         * @return Bank 银行
         */
        public FinanceEntity<Enterprise> getBank(final Enterprise enterprise) {
           return new FinanceEntity<Enterprise>(enterprise);
        }
        
        /**
         * 获取一个 金融主体
         * @param id 企业id
         * @return Bank 银行
         */
        public Bank getBank(final int id, final boolean b) {
            return new Bank(this.getEnterprise(id, b));
        }
        
        /**
         * 获得银行对象：可以进行会计记账,任何企业 都是一个银行对象
         * @param id 企业id
         * @return Bank
         */
        public Bank getBank(final int id) {
            return new Bank(this.getEnterprise(id, false));
        }

        /**
         * 加载订单
         * @param id 订单id
         * @return Order订单对象
         */
        public Order<?> getOrder(final int id){
            final var sql = FT("select * from t_contract where id=$0",id);
            //println(sql);
            final var contract = dataApp.sql2maybe(sql).map(e -> {
                final var partA = MyERPApp.this.getEnterprise(e.i4("partA"));
                final var partB = MyERPApp.this.getEnterprise(e.i4("partB"));
                //println(e);
                final var c = new Contract(e.i4("id"),partA,partB);
                c.createTime = e.ldt("create_time");
                c.name = e.str("name");
                return c;
            }).orElse(null);
            
            final var order = new Order<>(contract);
            //println("order",id,order);
            return order;
        }

        /**
         * 获得产品流
         * 
         * @return [Product]
         */
        public Stream<Product> getProductS() {
            return dataApp.sqlqueryS("select * from t_product").map(Product::new);
        }

        /**
         * 获取货币
         * 
         * @param moneyId 货币id
         * @return 货币对象
         */
        public Money getMoney(int moneyId) {
            return this.financeApp.getMoneyById(moneyId);
        }
        
        /**
         * 产品对象
         * @param name 产品名
         * @return 产品对象
         */
        public Product productOf(String name) {
            return dataApp.sql2maybe(FT("select * from t_product where name='$0'",name)).map(Product::new).orElse(null);
        }

        /**
         * 金融模块
         * 
         * @return
         */
        public FinanceApp getFinance() {
            return this.financeApp;
        }
        
        /**
         * 会计交易
         * @param acct_txs
         * @param <T>
         */
        @SafeVarargs
        public final <T extends FinanceEntity<?>> void withAcctTransaction(final BasePair<T,IRecord> ... acct_txs) {
            this.withAcctTransaction((MyDataApp) null, acct_txs);
        }
        
        /**
         * 会计交易的处理
         * 
         * @param acct_txs 会计交易 集合
         */
        @SafeVarargs
        public final <T extends FinanceEntity<?>> void withAcctTransaction(MyDataApp dataApp,final BasePair<T,IRecord> ... acct_txs) {
            
            // 开启数据会话
            ( dataApp==null ? this.dataApp : dataApp ).withTransaction(sess -> {
                for ( final var acct_tx : acct_txs ) {
                    final var finance_entity = acct_tx._1(); // 提取金融实体
                    final var rec = acct_tx._2(); // 提取 交易交易
                    
                    // 账套编号(会计主体id) 一般用财务实体编号 给予 表示 , 比如 银行的id。 
                    // 分录( t_entity_item_rel 的 row) 通过 ref(ownerid) 来 记录 ownerid, 这样 我们
                    // 在以后 查询的时候 就可以 通过 ref来提取 属于 该 financeEntity 的 复式记账分录列表。
                    final var ownerid = finance_entity.getId();  // 会计主体id
                    
                    // 生成会计交易
                    final var trans = REC("name",rec.str("name"),"create_time",LocalDateTime.now()); // 会计交易
                    final int transid = sess.sql2execute2int(SQL.of("t_transaction", trans).insert()); // 会计交易 id
                    
                    // 分录明细
                    final var createTime = rec.get("create_time",LocalDateTime.now()); // 创建时间
                    final var entries = rec.lla("entries", IRecord.class); // 提取记账分录
                    final var items = new LinkedList<IRecord>(); // 会计分录
                    final var fracf = NumberFormat.getInstance(); // 小数部分 子科目编号 0 表示默认子科目
                    final var intf = NumberFormat.getInstance(); // 整数部分 主科目编号
                    fracf.setMaximumIntegerDigits(0); fracf.setMinimumFractionDigits(1); fracf.setGroupingUsed(false); // 小数部分 
                    intf.setMaximumFractionDigits(0); intf.setGroupingUsed(false); // 整数部分
                    
                    for(final var entry : entries) {
                        
                        // 分析账户结构
                        final var _acctid = entry.dbl("acctid"); // 账户编码
                        final var acctid = intf.format(_acctid); // 提取账户编码
                        final var subacctid = Optional.of(fracf.format(_acctid)).map(e->e.matches("\\s*")?".0":e).map(e->e.substring(1)).get(); // 子科目id
                        final var acct_sql0 = FT("select name from t_coa where code ='$0.$1'", acctid,subacctid); // 提取 账户名称 - 带有 子科目
                        final var acct_sql1 = FT("select name from t_coa where code ='$0.0'", acctid); // 提取 账户名称 - 不带有子科目
                        final var acct = entry.get("acct",sess.sql2maybe(acct_sql0) // 带有子科目的 账户检索
                            .or(()->trycatch((String sql)->sess.sql2maybe(sql)).apply(acct_sql1)) // 舍弃子科目账户id重新检索 即 只检索 主科目id
                            .map(e -> e.str("name")).orElse("-")); // 获取 账户名称
                        
                        // 提取分录明细
                        final var drcr = entry.get("drcr"); // 科目的借贷方向
                        final var money = entry.get("money", 1); // 货币代码 默认为 人民币
                        final var amount = entry.get("amount"); // 账户的金额
                        final var description = FT("$0",entry.get("name", acct)); // 科目名称
                        final var path = "D:/sliced/files/products/valuables/gold.jpg"; // 默认图片路径
                        
                        // entity_id:交易代码, item_id:账户id, optional:子科目账户, quantity:货币代码, ref: 会计主体id
                        items.add(REC("path", path, "drcr", drcr, "name", "t_transaction/t_coa", "entity_id", transid, "item_id", acctid, 
                            "optional", subacctid , "amount", amount, "flag", 0, "quantity", money, "ref", ownerid, // ref 用于标识 该分录具体输入 哪个账套 如 财务(银行)实体
                            "description", description,"create_time",createTime));
                        // println(entry);
                    } // for
                    
                    final var flds = items.get(0).keys();
                    final var tpl = FT("insert into t_entity_item_rel (flds$) values ${foreach item in items (values$)}",
                        REC("flds$", JOIN(flds, ","), "values$", JOIN(flds.stream(),i -> "item." + i, ","))); // 明细项目的插入模板
                    final var sql = nsql(tpl, REC("items", items));
                    
                    //println(sql.string2());
                    sess.sql2execute2int(sql); // 存入数据
                }// for recs
            }); // withTransaction
        } // withTransaction
        
        /**
         * 生成 行项目
         * 
         * @param n 数量
         * @return LineItem 生成器
         */
        public Function<PriceQuote, LineItem> priceQuote2lineItem(final int n) {
            return priceQuote -> new LineItem(priceQuote, n);
        }
        
        /**
         * 生成 行项目
         * 
         * @param <T> 数量类型
         * @return LineItem 生成器 (priceQuote,quantiy)->LineItem
         */
        public <T extends Number> Function<BasePair<PriceQuote, T>, LineItem> priceQuote2lineItem() {
            return bp -> new LineItem(bp._1(), (Number) bp._2());
        }
        
        protected final FinanceApp financeApp;
        protected final MyDataApp dataApp;
        protected final Jdbc jdbc;
    }

    /**
     * 数据初始化
     */
    @Test
    public void initialize() {
        final var partA = erpApp.getEnterprise(1, true);
        final var partB = erpApp.getEnterprise(2, true);
        final var storeA = partA.store;
        final var storeB = partB.store;
        erpApp.dataApp.sqlqueryS("select * from t_house").collect(XVec.xveclc()).shuffle().head(2).forEach(e -> {
            storeA.addHouse(storeA.new WareHouse(e));
            storeB.addHouse(storeB.new WareHouse(e));
        });
        final var productPrices = erpApp.getProductS().map(pct->bp(pct, erpApp.financeApp.rmb(pct.getPrice().getAmount()))).collect(XVec.xveclc()); // 产品价格
        var catalog = partA.buildCatalog(REC("name", "我的比萨", "symbol","PIZZA"), 0.2, 0.17, productPrices.shuffle().resize(10).list()); // 创建产品目录
        catalog = partA.buildCatalog(REC("name", "我的饺子", "symbol","JIAOZI"), 0.2, 0.17, productPrices.shuffle().resize(10).list()); // 创建产品目录
        catalog = partA.buildCatalog(REC("name", "我的包子", "symbol","BAOZI"), 0.2, 0.17, productPrices.shuffle().resize(10).list()); // 创建产品目录
        catalog = partA.buildCatalog(REC("name", "我的煎饼", "symbol","JIANBING"), 0.2, 0.17, productPrices.shuffle().resize(10).list()); // 创建产品目录
        catalog = partA.buildCatalog(REC("name", "我的馄饨", "symbol","HUNDUN"), 0.2, 0.17, productPrices.shuffle().resize(10).list()); // 创建产品目录
        catalog = partA.buildCatalog(REC("name", "我的汤圆", "symbol","TANGYUAN"), 0.2, 0.17, productPrices.shuffle().resize(10).list()); // 创建产品目录
        catalog = partB.buildCatalog(REC("name", "我的产品", "symbol","CHANPIN"), 0.2, 0.17, productPrices.shuffle().resize(10).list()); // 创建产品目录
        partA.setDefaultCatalog(catalog); // 设置默认产品目录
        partA.getDefaultCatalog().getPriceQuotes().forEach(e -> {
            println(e);
        });
        
        partB.buildCatalog(REC("name","我的元宵","symbol","JIAOZI"), 0.2, 0.17, productPrices.shuffle().resize(10).list()); // 创建产品目录
        
        final var catalog_query = erpApp.dataApp.tbl2maybe_rec_f("t_catalog");
        final var opt = catalog_query.apply(REC("name","我的饺子")).map(e->erpApp.new Catalog(e));
        if(opt.isPresent()) {
            println("产品目录:",opt.get(),opt.get().getId());
        }
        
        println("默认的catalog",partA.getCatalog(partA.getDefaultCatalog().getId()));
    }
    
    /**
     * 金融数据准备
     */
    public void finance_initialize() {
        
        final Consumer<List<String>> sql_execute_batch  = sqls -> { // 批量的 sql 语句执行
            erpApp.dataApp.withTransaction( sess -> {
                for(var sql:sqls) sess.sql2execute(sql);
            });
        };
        
        final Consumer<String> sql_execute  = sql -> { // sql 语句执行
            sql_execute_batch.accept(IRecord.L(sql));
        };
        
        final Function<IRecord,String> coa_insert_sql = rec -> { // id, code , name, type, remarks
            final var flds = "id,code,name,type,path,price,description,remarks";
            final Object[] aa = CONS(flds,Stream.of("id,code,name,type,name,remarks".split(",")).map(rec::get).toArray(String[]::new)); // 注意这里把 description 对应到了 name
            final var line = FT("insert into t_coa($0) values ('$1','$2','$3','$4','D:/sliced/files/products/valuables/gold.jpg','1.0','$5','$6')",aa);
            return line;
        };
        
        Stream.of( // 数据清空
            "delete from t_coa where id < 0", // 清空 中央银行账户
            "delete from t_coa where id > 10000", // 清空 企业存款 账户
            "delete from t_entity_item_rel where name = 't_transaction/t_coa'" // 清空 记账分录
        ).peek(System.out::println).forEach(sql_execute); // 清空数据

        println("------------------------------------------");

        Stream.of(// 创建账户
            // 中央银行 账户
            A("-1", "9000.0", "货币发行", "负债类", "中央银行科目"), // 货币发行
            A("-2", "9001.0", "人民币-M0", "资产类", "中央银行科目"), // 人民币-M0 账户
            A("-3", "9002.0", "银行准备金", "负债类", "中央银行科目"), // 银行准备金
            A("-4", "9003.0", "客户存款", "负债类", "银行科目"), // 客户存款
            // 银行准备金 账户
            A("-90020015", "9002.15", "银行准备金-工商银行", "负债类", "中央银行科目"), // 商业银行在央行开户 - 工商银行
            A("-90020029", "9002.29", "银行准备金-农业银行", "负债类", "中央银行科目"), // 商业银行在央行开户 - 农业银行
            // 企业存款 账户
            A("90030001", "9003.1", "客户存款-沃尔玛", "资产类", "银行科目-客户存款"), // 企业开户 银行存款-沃尔玛 , id = 基本账户+企业id
            A("90030002", "9003.2", "客户存款-国家电网", "资产类", "银行科目-客户存款") // 企业开户 银行存款-国家电网 , id = 基本账户+企业id
        ).map(rb("id,code,name,type,remarks")::build).map(coa_insert_sql).peek(System.out::println)
        .forEach(sql_execute); // 创建账户
    } // finance_initialize

    @Test
    public void foo() {
        // this.initialize();
        
        final var partA = erpApp.getEnterprise(1,true);
        final var partB = erpApp.getEnterprise(2,true);
        println("\n-----------------------------------");
        println(" partA 企业 的库房：",partA);
        partA.getHouses().forEach(h -> {
            println("-- ", h);
        });
        
        println(" partA 企业 的产品目录：",partA);
        partA.getCatalogs().forEach(e->{
            println("-- catalog",e);
            e.getPriceQuotes().forEach(quote->{
                println("-- --",quote);
            });
        });
        
        println("-----------------------------------");
        
        final var df = partA.store.trialBalance();
        println("\n------------------产品存货状态------------------\n",partA.getDefualtHouse(),df);
        
        final var dfm = partA.getDefualtHouse().houseJounal(partB);
        println("\n------------------出库库状态------------------\n",partA.getDefualtHouse(),dfm);
        
        final Function<Number,Function<PriceQuote,LineItem>> ol = n->priceQuote->erpApp.new LineItem(priceQuote,n); // 购物选项
        final var priceQuotes = partA.getDefaultCatalog().getPriceQuotes().stream().collect(XVec.xveclc()); // 产品报价
        final var po = partA.purchase(partB,priceQuotes.shuffle().head(2).fmap(ol.apply(1)).toArray());
        println(partA,"创建一个采购订单",po);
        po.persist();
        final var so = partB.sale(partA,priceQuotes.shuffle().head(2).fmap(ol.apply(1)).toArray());
        println(partB,"创建一个销售订单",so);
        so.persist();
        println("\n============================================================================");
        println("采购订单@",partA);
        println("---------------------------------------------------------------------------");
        partA.getPuchaseOrders().forEach(e->{
            println(partA,"PO",e.refresh());
            e.getLineItems().forEach(item->{
                println("--",item);
            });
            
            final var inbound = erpApp.new StoreBill<>(e).map(p->erpApp.new InboundBill(p), 0.5).setOwner(partA);
            partA.store.inbound(inbound);
            println("--",partA,inbound);
        });
        
        println("============================================================================");
        println("销售订单@",partB);
        println("---------------------------------------------------------------------------");
        partB.getSaleOrders().forEach(e->{
            println(partB,"SO",e.refresh());
            e.getLineItems(true).forEach(item->{
                println("--",item);
            });
            
            final var outbound = erpApp.new StoreBill<>(e).map(p->erpApp.new OutboundBill(p), 0.5*Math.random()).setOwner(partB);
            partB.store.outbound(outbound);
            println("--",partB,outbound);
        });
       
    }
    
    @Test
    public void bar() {
        final var partA = erpApp.getEnterprise(1,true);
        final var partB = erpApp.getEnterprise(2,true);
        println(" partA 企业：",partA);
        
        partA.getPuchaseOrders().forEach(e->{
            final var sb = erpApp.new StoreBill<>(e);
            final var inbound = sb.map(p->erpApp.new InboundBill(p), 0.5);
            println(inbound);
            inbound.baseline().foreach(p->{
                println(p);
            });
        });
        
        println(partA.store.trialBalance());
        partA.getHouses().forEach(h -> {
            println("-->", h);
        });
        
        final var df = partA.store.getDefaultWareHouse().houseJounal(partA);
        println(df);
        final var df2 = partA.store.getDefaultWareHouse().houseJounal(partB);
        println(df2);
    }
    
    /**
     * 
     * 银行系统的简单测试
    */
    @Test
    public void quz() {
        
        // 央行发行货币
        final Function<IRecord, Consumer<IRecord>> print_money = param -> rec ->{ // 存款函数
            
            final var amount = rec.dbl("amount"); // 存款金额
            final var central_bank = param.get("central_bank",FinanceEntity.class);  //中国银行 央行
            final var line = FT("$0-发行货币-M0",central_bank.getName()); // 转账说明
            central_bank.withAcctTransaction(REC("name",line,"entries", L(
                REC("drcr",0,"acctid","9001.0","amount",amount), // 人民币M0
                REC("drcr",1,"acctid","9000.0","amount",amount) // 货币发行
            )));// 创建交易
            
        };
        
        // 用户存款：资金 在 不同银行间 的 出入运动 称为 汇入，汇出; 资金在同一银行的不同账户之间 的 运动 称为 转入(进入到该账号),转出（从该账户中出去)。
        // 转 就是来回，有来有回的意思，强调 相互来往 的 彼此增减。注意 转入&转出 是相对于 业务逻辑 上的 先后概念，即 从哪里出来 又进入到了哪里。
        // 并非 就是 意味着 余额 的增出减，具体的余额变化 则 需要 根据科目类型 来 进行确定。
        final Function<IRecord, Consumer<IRecord>> deposit = param -> rec ->{ // 存款函数
            
            // 银行信息
            final var central_bank = param.get("central_bank",FinanceEntity.class);  // 央行
            final var bank = param.get("bank",FinanceEntity.class);  // 商业银行
            final var legal_reserve_ratio = param.dbl("legal_reserve_ratio"); // 法定准备
            final var excess_reserve_ratio = param.dbl("excess_reserve_ratio"); // 超额准备金
            final var reserve_ratio = legal_reserve_ratio+excess_reserve_ratio; // 准备金率
            final var depositorid = param.get("depositorid"); // 存款人编号
            final var create_time = param.get("create_time",LocalDateTime.now()); // 创建日期
            
            // 主要交易账户: 央行
            final var central_cash_acct = FT("9001.0"); // 央行：央行货币账户
            final var central_depositor_acct = FT("$0.$1",9002,bank.getId()); // 央行：存款人账号 即 商业银行 位于 央行的 资金(准备金)账户
            
            // 主要交易账户：商业银行
            final var bank_depositor_acct = FT("$0.$1",9003,depositorid); // 商业银行：存款人账户 即 用户资金账户
            final var bank_due_from_central_bank_acct = FT("1003"); // 商业银行：存放中央银行账户
            final var bank_cash_acct = FT("1001"); // 商业银行：库存现金 账户
            final var bank_savings_absorbtion_acct = 2012; // 商业银行：吸收存款账户
            
            // 存款信息
            final var amount = rec.dbl("amount"); // 存款金额
            
            // 商业银行 吸收存款：将 用户存款 即 吸收存款 按准备金提取比率 分配到 库存现金 和 存放中央银行账户
            final var line1 = FT("$0-商业银行-吸收存款-把-用户($1)的存款资金($2)-通过-吸收存款账户($3)-分配到-库存现金账户($4)&存放中央银行账户($5)",
                bank.getName(), depositorid, amount, bank_savings_absorbtion_acct, bank_cash_acct, bank_due_from_central_bank_acct); // line1
            final var rec1 = REC("name",line1,"entries", L( // 分录明细
                REC("drcr",0,"acctid",bank_cash_acct,"amount",amount*(1-reserve_ratio)), // 库存现金
                REC("drcr",0,"acctid",bank_due_from_central_bank_acct,"amount",amount*reserve_ratio), // 存放中央银行
                REC("drcr",1,"acctid",bank_savings_absorbtion_acct,"amount",amount) // 吸收存款
            ),"create_time",create_time);// 商业银行-吸收存款
            
            // 商业银行 存款转账： 把客户的存款 转入 客户的资金账户
            final var line2 = FT("$0-商业银行-存款转账-把-用户($1)的存款资金($2)-通过-银行的吸收存款账户($3)-转入-银行的用户资金账户($4)",
                bank.getName(), depositorid, amount,bank_savings_absorbtion_acct, bank_depositor_acct);
            final var rec2 = REC("name",line2,"entries", L( // 分录 明细
                REC("drcr",0,"acctid",bank_savings_absorbtion_acct,"amount",amount),// 吸收存款
                REC("drcr",1,"acctid",bank_depositor_acct,"amount",amount) // 沃尔玛的 工商银行的账户
            ),"create_time",create_time);// 商业银行 存款转账
            
            // 中央银行 结算提留存款准备金：把提留的存款准备金 分配到 央行的货币账户
            final var line3 = FT("$0-中央银行-结算提留存款准备金-把-吸收存款($1)按准备金比率($2)提取-通过-商业银行账户即央行的存款人账户($3)-分配到-央行的货币账户($4) 并 给予监管",
                central_bank.getName(), amount, reserve_ratio, central_cash_acct, central_depositor_acct);
            final var rec3 = REC("name",line3,"entries", L( // 分录 明细
                REC("drcr",0,"acctid",central_cash_acct,"amount",amount*reserve_ratio), // RMB-M0
                REC("drcr",1,"acctid",central_depositor_acct,"amount",amount*reserve_ratio) // 商业银行准备金
            ),"create_time",create_time);// 中央银行 结算提留存款准备金
            
            // 数据保存
            erpApp.withAcctTransaction( bp(bank,rec1), bp(bank,rec2), bp(central_bank,rec3) );
            
        };
        
        // 用户取款，资金 在 不同银行间 的 出入运动 称为 汇入，汇出; 资金在同一银行的不同账户之间 的 运动 称为 转入(进入到该账号),转出（从该账户中出去)。
        // 转 就是来回，有来有回的意思，强调 相互来往 的 彼此增减。注意 转入&转出 是相对于 业务逻辑 上的 先后概念，即 从哪里出来 又进入到了哪里。
        // 并非 就是 意味着 余额 的增出减，具体的余额变化 则 需要 根据科目类型 来 进行确定。
        final Function<IRecord, Consumer<IRecord>> withdrawl = param -> rec ->{ // 存款函数
            
            // 银行信息
            final var bank = param.get("bank",FinanceEntity.class);  // 商业银行
            final var entity = param.get("withdrawler",Entity.class);  // 提款人
            
            // 转账明细
            final var amount = rec.get("amount",0); // 转账金额
            final var create_time = rec.get("create_time",LocalDateTime.now()); // 创建日期
            final var bank_withdraw_dr_acct = FT("$0.$1",9003,entity.getId()); // 取款人账号
            final var bank_cash_cr_acct = FT("9001.0"); // 银行的库存现金账号
            
            // 商业银行 用户取款
            final var description = rec.get("description",FT("$0-商业银行-用户($1)-取款($2))",bank.getName(),entity.getId(),amount)); // 转账说明
            bank.withAcctTransaction(REC("name",description,"entries", L( // 分录 明细
                REC("drcr",0,"acctid",bank_withdraw_dr_acct,"amount",amount), // 转入账户 抵减余额
                REC("drcr",1,"acctid",bank_cash_cr_acct,"amount",amount) // 转出账户 抵减余额
            ),"create_time",create_time));
            
        };
        
        // 银行内部转账，资金 在 不同银行间 的 出入运动 称为 汇入，汇出; 资金在同一银行的不同账户之间 的 运动 称为 转入(进入到该账号),转出（从该账户中出去)。
        // 转 就是来回，有来有回的意思，强调 相互来往 的 彼此增减。注意 转入&转出 是相对于 业务逻辑 上的 先后概念，即 从哪里出来 又进入到了哪里。
        // 并非 就是 意味着 余额 的增出减，具体的余额变化 则 需要 根据科目类型 来 进行确定。
        final Function<IRecord, Consumer<IRecord>> transfer = param -> rec ->{ // 存款函数
            
            // 银行信息
            final var bank = param.get("bank",FinanceEntity.class);  // 商业银行
            final var from_entity = rec.get("from_entity",Entity.class);  // 转出账户
            final var to_entity = rec.get("to_entity",Entity.class);  // 转入账户
            
            // 转账明细
            final var amount = rec.get("amount",0); // 转账金额
            final var create_time = rec.get("create_time",LocalDateTime.now()); // 创建日期
            final var bank_depositor_dr_acct = FT("$0.$1",9003,from_entity.getId());
            final var bank_depositor_cr_acct = FT("$0.$1",9003,to_entity.getId());
            
            // 商业银行 内部转账：付款(转出)人 转账 收款(转入)人
            final var description = rec.get("description",FT("$0-商业银行-内部转账-付款(转出)账户($1)-转账($2)到-收款(转入)人账户($3)",
                bank.getName(),bank_depositor_dr_acct,amount,bank_depositor_cr_acct)); // 转账说明
            bank.withAcctTransaction(REC("name",description,"entries", L( // 分录 明细
                REC("drcr",0,"acctid",bank_depositor_dr_acct,"amount",amount), // 转出账户
                REC("drcr",1,"acctid",bank_depositor_cr_acct,"amount",amount) // 转入账户
            ),"create_time",create_time));
            
        };
        
        // 跨行转账, 资金 在 不同银行间 的 出入运动 称为 汇入，汇出; 资金在同一银行的不同账户之间 的 运动 称为 转入(进入到该账号),转出（从该账户中出去)。
        // 转 就是来回，有来有回的意思，强调 相互来往 的 彼此增减。注意 转入&转出 是相对于 业务逻辑 上的 先后概念，即 从哪里出来 又进入到了哪里。
        // 转入转出 常用语 在 一个复杂 流程的 具体 中间环节中 标明 资金的 进出方向，比如 A -> B -> C -> D 。 宏观结论（净流量）上 是 资金从 A流到了D。
        // 但是 A 代表 净流出 D 代表 净流入。但 中间的 节点 B,C 则是 既有 流入 又有 流出 也就是 出入 活动 是 来回的周期周期往复式进行的，以 轮转&回环的。
        // 从一开始的净流量为0 到 结尾的时候 净流量还是为 0 即 所谓的轮转回环的意思; 即 先从 从A转出，转入B; 再 从B转出转入C ，最后从C转出，转入D。
        // 并非 就是 意味着 余额 的增出减，具体的余额变化 则 需要 根据科目类型 来 进行确定。
        final Function<IRecord, Consumer<IRecord>> bank2bank_transfer = param -> rec ->{ // 存款函数
            
            final var central_bank = param.get("central_bank",(FinanceEntity<Object>)null);  // 央行
            final var from_bank = param.get("from_bank",(FinanceEntity<Object>)null);  // 汇出行
            final var to_bank = param.get("to_bank",(FinanceEntity<Object>)null);  // 汇入行
            
            final var from_entity = rec.get("from_entity",Entity.class);  // 转出账户
            final var to_entity = rec.get("to_entity",Entity.class);  // 转入账户
            
            final var amount = rec.get("amount",0); // 转账金额
            final var create_time = rec.get("create_time",LocalDateTime.now()); // 创建日期
            
            // 央行 清分指令：通过央行进行资金划转
            final var central_bank_dr_acct = FT("$0.$1",9002,from_bank.getId()); // 付款(汇出)行账户
            final var central_bank_cr_acct = FT("$0.$1",9002,to_bank.getId()); // 收款(汇入)行账户
            // 中央银行 银行间转账：付款(汇出）行账户 转（汇）入 收款(汇入)行账户
            final var line1 = rec.get("description",FT("$0-中央银行-银行间转账-付款(汇出)行账户($1)-转出($2)-收款(汇入)行账户($3) ",
                central_bank.getName(),central_bank_dr_acct,amount,central_bank_cr_acct)); // 转账说明
            final var central_rec = REC("name",line1,"entries", L( // 分录 明细
                REC("drcr",0,"acctid",central_bank_dr_acct,"amount",amount), // 付款(汇出)行账户 ， 注意 银行类科目 借方 表示减少
                REC("drcr",1,"acctid",central_bank_cr_acct,"amount",amount) // 收款(汇入)行账户 ， 注意 银行类科目 待方 表示增加
            ),"create_time",create_time); // 央行 清分指令
            
            // 汇出行 账目操作
            final var from_bank_dr_acct = FT("$0.$1",9003,from_entity.getId()); // 付款(汇出)人账户 ,  注意：银行分录 借贷方向 与 企业分录 方向是反的, 借入 代表 抵减。
            // 注意：由于付款人账户(客户存款)是贷方余额，所以 转出操作 需要 借计 付款账户(客户存款)，相应的，接收操作 需要 贷计 收款账户(库存现金), 实际是 减少了 收款账户的余额。
            // 即 转入消减，用转入的方式消减了余额，成为 入消。注意这里的 用词：
            // 1) 转入 是 对应于 所在业务逻辑的 语境位置,即 入的位置, 处于接收方 是 相对于付款人的。
            // 2) 消减 是 对应于 科目自身余额的增减变化，即 贷记金额 减少了余额，由此 消减。
            final var from_bank_cr_acct = FT("1001"); // 中间收款(转入消减)人账户 ，用库存现金 来接收 付款人支付的金额，
            // 汇出银行 银行间转账：付款(汇出)人账户 转出 中间收款(转入消减)人账户
            final var line2 = rec.get("description",FT("$0-汇出银行-银行间转账-付款(转出)人账户($1)-转出($2)-中间收款人(转入消减)账户($3)",
                from_bank.getName(),from_bank_dr_acct,amount,from_bank_cr_acct)); // 转账说明
            final var from_bank_rec = REC("name",line2,"entries", L( // 分录 明细
                REC("drcr",0,"acctid",from_bank_dr_acct,"amount",amount), // 付款(转出)人账户
                REC("drcr",1,"acctid",from_bank_cr_acct,"amount",amount) //  中间收款人(转入消减)账户
            ),"create_time",create_time); // 汇出银行账目操作
            
            // 汇入行 账目操作
            final var to_bank_dr_acct = FT("1001"); //  中间付款人(转出增加)账户 , 转出增加：出增
            final var to_bank_cr_acct = FT("$0.$1",9003,to_entity.getId()); // 收款(汇入)人账户
            // 汇入银行 银行间转账：中间付款账户 转入 收款(汇入)账户
            final var line3 = rec.get("description",FT("$0-汇入银行-银行间转账-中间付款人(转出增加)账户($1)-转出($2)-收款(汇入)人账户($3)",
                to_bank.getName(),to_bank_dr_acct,to_bank_cr_acct,amount)); // 转账说明
            final var to_bank_to_rec = REC("name",line3,"entries", L( // 分录 明细
                REC("drcr",0,"acctid",to_bank_dr_acct,"amount",amount), // 中间付款人(转出增加)账户
                REC("drcr",1,"acctid",to_bank_cr_acct,"amount",amount) // 收款(汇入)人账户
            ),"create_time",create_time);
            
            erpApp.withAcctTransaction( bp(central_bank,central_rec), bp(from_bank,from_bank_rec), bp(to_bank,to_bank_to_rec) );
            
        };
        
        final var central_bank = erpApp.getBank(35,false);  // 中国银行 央行
        final var icbc_bank = erpApp.getBank(15,false);  // 工商银行
        final var abc_bank = erpApp.getBank(29,false);  // 农业银行
        final var wmt = erpApp.getEnterprise(1,true);  // 沃尔玛
        final var sg = erpApp.getEnterprise(2,true);  // 国家电网
        
        // 财务数据初始化
        this.finance_initialize();
        println("------------------------------------------");
        
        println(central_bank);
        println(icbc_bank);
        
        println("------------------------------------------");
        
        // 中央银行信息
        final var profile = REC("legal_reserve_ratio",0.3,"excess_reserve_ratio",0.2,"central_bank",central_bank);
        
        // 农业银行 存款
        final var abc_wmt_deposit = deposit.apply(profile.derive("bank",abc_bank,"depositorid",wmt.getId())); // 沃尔玛企业-农业银行-存款
        final var abc_sg_deposit = deposit.apply(profile.derive("bank",abc_bank,"depositorid",sg.getId())); // 国家电网企业-农业银行-存款
        
        // 工商银行 存款
        final var icbc_wmt_deposit = deposit.apply(profile.derive("bank",icbc_bank,"depositorid",wmt.getId())); // 沃尔玛企业-工商银行-存款
        final var icbc_sg_deposit = deposit.apply(profile.derive("bank",icbc_bank,"depositorid",sg.getId())); // 国家电网企业-工商银行-存款
        
        // 资金的转账
        final var icbc_transfer = transfer.apply(profile.derive("bank",icbc_bank)); // 工商银行-内部转账
        final var icbc2abc_transfer = bank2bank_transfer.apply(profile.derive("from_bank",icbc_bank,"to_bank",abc_bank)); // 工商银行 向 农业银行 进行 转账
        
        // 银行取款
        final var icbc_wmt_withdrawl = withdrawl.apply(profile.derive("bank",icbc_bank,"withdrawler",wmt.getFinanceEntity())); // 沃尔玛 从 工商银行 取款
        final var abc_wmt_withdrawl = withdrawl.apply(profile.derive("bank",abc_bank,"withdrawler",wmt.getFinanceEntity())); // 沃尔玛 从 农业银行 取款
        
        // 销售模拟
        final var now = LocalDateTime.now(); // 计入当前系统时间
        final var trans_rb = IRecord.rb("name,now,entries"); // 交易构建器
        final var entry_rb = IRecord.rb("drcr,acctid,amount"); // 分录构建器
        final var acct_txs = A( // 销售业务的 会计交易 的 记账 模拟
            trans_rb.get("销售商品收入", now, L( // 分录 明细
                entry_rb.get(0,1001,117), // 库存现金
                entry_rb.get(1,6001,100), // 主营业务收入
                entry_rb.get(1,2221,17) // 应交税费
            )), trans_rb.get("产品成本结转", now, L( // 分录 明细
                entry_rb.get(0,6401,50), // 主营业务成本
                entry_rb.get(1,1406,50) // 库存商品
            )), trans_rb.get("销售商品收入", now, L( // 分录 明细
                entry_rb.get(0,1001,117), // 库存现金
                entry_rb.get(1,6001,100), // 主营业务收入
                entry_rb.get(1,2221,17) // 应交税费
            )), trans_rb.get("产品成本结转", now, L( // 分录 明细
                entry_rb.get(0,6401,50), // 主营业务成本
                entry_rb.get(1,1406,50) // 库存商品
            ))
        ); // 销售模拟
        
        final var wmt_entity = wmt.withAcctTransaction ( acct_txs ); // wmt 的业务运营 的 账务处理, 增加 库存现金
        final var sg_entity = sg.withAcctTransaction ( acct_txs ); // sg 的业务运营 的 账务处理,增加 库存现金
        
        // 中央银行 发行货币
        print_money.apply(profile).accept(REC("amount",10000)); // 发行货币的规模
        
        // 工商银行 用户存款 WMT
        icbc_wmt_deposit.accept(REC("name",FT("工商银行 用户存款-$0",LocalDateTime.now()),"amount",100)); // 存款100 万
        wmt_entity.withAcctTransaction ( // 工商银行 用户存款 WMT
            trans_rb.get("工商银行 用户存款", now, L( // 分录 明细
                entry_rb.get(0,1002.15,100), // 1002银行存款.15工商银行 账户 增加 银行存款
                entry_rb.get(1,1001,100) // 库存现金账户 抵减 库存现金
            )
        ));
        
        // 工商银行 用户存款 SG
        icbc_sg_deposit.accept(REC("name",FT("工商银行 用户存款-$0",LocalDateTime.now()),"amount",100)); // 存款100 万
        sg_entity.withAcctTransaction ( // 工商银行 用户存款 SG
            trans_rb.get("工商银行 用户存款", now, L( // 分录 明细
                entry_rb.get(0,1002.15,100), // 1002银行存款.15工商银行 账户 增加 银行存款
                entry_rb.get(1,1001,100) // 库存现金账户 抵减 库存现金
            )
        ));
        
        // 农业银行 用户存款 WMT
        abc_wmt_deposit.accept(REC("name",FT("农业银行 用户存款-$0",LocalDateTime.now()),"amount",100)); // 存款100 万
        wmt_entity.withAcctTransaction ( // 农业银行 用户存款 WMT
            trans_rb.get("农业银行 用户存款", now, L( // 分录 明细
                entry_rb.get(0,1002.29,100), // 1002银行存款.29农业银行 账户 增加 银行存款
                entry_rb.get(1,1001,100) // 库存现金账户 抵减 库存现金
            )
        ));
        
        // 农业银行 用户存款 SG
        abc_sg_deposit.accept(REC("name",FT("农业银行 用户存款-$0",LocalDateTime.now()),"amount",100)); // 存款100 万
        sg_entity.withAcctTransaction ( // 农业银行 用户存款 SG
            trans_rb.get("农业银行 用户存款", now, L( // 分录 明细
                entry_rb.get(0,1002.29,100), // 1002银行存款.29农业银行 账户 增加 银行存款
                entry_rb.get(1,1001,100) // 库存现金账户 抵减 库存现金
            )
        ));
        
        // 银行转账演示 - 银行内转账 - 需要 银行 与 企业同时记账
        icbc_transfer.accept(REC("from_entity",wmt,"to_entity",sg,"amount",10)); // 沃尔玛(工商银行) 转账 10 给 国家电网(工商银行) 用于支付电费
        wmt_entity.withAcctTransaction ( // 工商银行 转账
            trans_rb.get("支付电费10", now, L( // 分录 明细
                entry_rb.get(0,6401,10), // 主营业务成本 增加
                entry_rb.get(1,1002.15,10) // 1002银行存款.15工商银行 抵减 
            )
        ));
        sg_entity.withAcctTransaction ( // 工商银行 转账
            trans_rb.get("供电输入10", now, L( // 分录 明细
                entry_rb.get(0,1002.15,10), // 1002银行存款.15工商银行 增加
                entry_rb.get(1,6001,10) // 主营业务收入 增加
            )
        ));
        
        // 银行转账演示 - 跨行转账 - 需要 银行 与 企业同时记账
        icbc2abc_transfer.accept(REC("from_entity",wmt,"to_entity",sg,"amount",20)); // 沃尔玛(工商银行) 转账 20 给 国家电网(农业银行银行) 用于支付电费
        wmt_entity.withAcctTransaction ( // 工商银行转出
            trans_rb.get("支付电费20", now, L( // 分录 明细
                entry_rb.get(0,6401,20), // 主营业务成本 增加 10
                entry_rb.get(1,1002.15,20) // 1002银行存款.15工商银行 抵减 
            )
        ));
        sg_entity.withAcctTransaction ( // 农业银行转入
            trans_rb.get("供电收入20", now, L( // 分录 明细
                entry_rb.get(0,1002.29,20), // 1002银行存款.29农业银行 增加
                entry_rb.get(1,6001,20) // 主营业务收入 增加
            )
        ));
        
        // 银行取款 工商银行
        icbc_wmt_withdrawl.accept(REC("amount",50)); // 从工商银行取款 50元
        wmt_entity.withAcctTransaction ( // 从工商银行取款
            trans_rb.get("从工商银行取款50", now, L( // 分录 明细
                entry_rb.get(0,1001,50), // 库存现金 增加 库存现金
                entry_rb.get(1,1002.15,50) // 1002银行存款.15工商银行 抵减 银行存款
            )
        ));
        
        // 银行取款 农业银行
        abc_wmt_withdrawl.accept(REC("amount",50)); // 从农业银行取款 50元
        wmt_entity.withAcctTransaction ( // 从农业银行取款
            trans_rb.get("从农业银行取款50", now, L( // 分录 明细
                entry_rb.get(0,1001,50), // 库存现金 增加 库存现金
                entry_rb.get(1,1002.29,50) // 1002银行存款.29农业银行 抵减 银行存款
            )
        ));
        
        // 银行 资产负债表
        println("中央银行 资产负债表\n",central_bank.trialBalance());
        println("ICBC 银行 的 资产负债表\n",icbc_bank.trialBalance());
        println("ABC 银行 的 资产负债表\n",abc_bank.trialBalance());
        
        // 企业 资产负债表
        println("SG 企业 的 资产负债表\n",sg_entity.trialBalance());
        println("WMT 企业 的 资产负债表\n",wmt_entity.trialBalance());
        
        // 企业的逆资产负债表
        println("企业的逆资产负债表 就是 按照 银行记账方式 即 借负债 贷资产 的方式 进行记账\n");
        println("WMT 企业 的 逆资产负债表 银行分列 - 客户存款账户的余额 对应 其 企业资产负债表 的 银行存款账户的 余额 \n",wmt.trialBalance(false,icbc_bank,abc_bank)); // 银行分列
        println("SG 企业 的 逆资产负债表 银行合列- 客户存款账户的余额 对应 其 企业资产负债表 的 银行存款账户的 余额 \n",sg.trialBalance(true,icbc_bank,abc_bank)); // 银行合列
    }
    
    /**
     * 获取 获取 企业的 信用额度 
     */
    @Test
    public void credit() {
        final var partA = erpApp.getEnterprise(1, true);
        final var partB = erpApp.getEnterprise(2, true);

        // partA 设置企业 partB 的 信用额度
        partA.updateCredit(partB, v -> {
            return v == null ? 100d : v + 100d;
        });

        // partB 设置企业 partA 的 信用额度
        partB.updateCredit(partA, v -> {
            return v == null ? 100d : v + 200d;
        });

        println(partA, partB, "的信用额度", partA.getCredit2(partB));
        println(partB, partA, "的信用额度", partB.getCredit2(partA));
        
        final var catalogName = "我的产品";
        final var catalogX = partB.catalogX(catalogName); // 提取产品目录
        println(partB, catalogName, catalogX);
        
        catalogX.resize(200).shuffle().slice(20)
        .forEach(xvec -> { // 生成订单
            final var orderLines = xvec.groupByX(o -> o, XVec::OF).fmap(p -> p.fmap2(XVec::size)).fmap(erpApp.priceQuote2lineItem()); // 生成订单行项目
            final var po = partA.purchase(partB, orderLines); // 生成订单
            println(po);
            po.forEach(e -> {
                println(e, e.product.price);
            });
            po.persist();
            println("Total:",po.getTotal());
        });
        
        // 修改产品报价单
        partB.catalogOf(catalogName).update(catalogX.list(e->REC("product",e.getProduct(),"price",e.getUnitPrice().amount*1.1,"tax",0.12)));
        
        // 指定具体产品名称的修改
        partB.catalogOf(catalogName).update( Stream.of("面粉,面粉1,冰鲜耗儿鱼,冻豆腐".split(",")) // 产品清单
            .map(name->REC("product",erpApp.productOf(name),"price",100,"tax",0.17))
            .collect(Collectors.toList())
        );
        
        // 删除指定产品
        //partA.catalogOf("我的煎饼").remove( Stream.of("冰鲜耗儿鱼,冻豆腐".split(",")).map(erpApp::productOf).collect(Collectors.toList()));
        
        partA.getPuchaseOrders().forEach(e -> {
            println("------------------",e);
        });
    }
    
    /**
     * 消息处理：<br>
     * 请与 https://gitee.com/gbench/hipponoe/blob/master/src/main/scala/gbench/tartarus/hipponoe/finance/acct/AccountingApp.scala 配合使用
     */
    @Test
    public void accounting() {
        final var trans_rb = IRecord.rb("entityid,name,now,entries"); // 交易构建器
        final var entry_rb = IRecord.rb("drcr,acctid,amount"); // 分录构建器
        final var topic = "erp-accounting2"; // 消息主题
        final var kafkaApp = new KafkaApp("localhost:9092", topic); // 业务app
        final var producer = kafkaApp.new Producer(); // 生产者
        final var dtfpat = CronTime.dtf("yyyy-MM-dd HH:mm:ss"); // 模式匹配
        final var acct_entityid = 10; // 会计主体
        final var admin = kafkaApp.new Admin(); // 队列管理端
        final var topics = admin.getTopics(); // 主题

        println("主题列表",topics);
        
        if (!topics.contains(topic)) {
            admin.createTopic(topic);
        }

        println("清空主题",admin.getTopics());

        for (int i = 0; i < 1000; i++) {
            try {
                Thread.sleep(500);
                final var dr_acct = 1002.15;
                final var cr_acct = Math.random() > .5 ? 6001 : 1001;
                final var rec = trans_rb.get(acct_entityid, "工商银行 用户存款", now().format(dtfpat), L( // 分录 明细
                        entry_rb.get(0, dr_acct, 100), // 1002银行存款.15工商银行 账户 增加 银行存款
                        entry_rb.get(1, cr_acct, 100) // 库存现金账户 或是 营业收入
                ));
                println(rec);
                producer.send(rec);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } // for

        producer.close();
    }
    
    /**
     * 门户关口
     */
    @Test
    public void portal() {
        final var products = erpApp.getProductS().collect(XVec.xveclc()).head(50);
        final var enterprises = erpApp.dataApp.sqlqueryS("select * from t_fortune").limit(10)
                .map(r->r.mutate(e->erpApp.new Enterprise(e)).initialize()).collect(XVec.xveclc());
        
        enterprises.forEach(enterprise->{
            println(enterprise);
            final var pct2prices = products.shuffle().fmap(pct->BasePair.bp(pct, erpApp.financeApp.rmb(pct.getPrice().getAmount()*(1+Math.random()*10))));
            final var catalog = enterprise.buildCatalog(REC("name","主营产品","symbol","PRIMARY"), 0.2, 0.17, pct2prices.head(10+Math.random()*100));
            println(catalog.collect(XVec.xveclc()));
        });
        
        println("----------------------------------------------------------------------");
        final var pcts = enterprises.flatMap(e->e.getCatalog().stream(q->BasePair.bp(q.getProduct().getName(), BasePair.bp(q, e)))).collect(PVec.pvec2clc());
        pcts.groupByX(e->e._1(),XVec::OF).forEach(e->{
            final var pct = e._1(); // 产品名称
            final var quotes = e._2().fmap(p->REC("name",p._2()._2(),"quotes",p._2()._1())); // 报价列表
            println(pct,quotes);
        });
        
    }
    
    final MysqlDatabase database = IJdbcApp.newNspebDBInstance("db3.sql", MysqlDatabase.class); // 创建数据库像
    final IRecord proxy = database.getProxy(); // 提取代理对象
    final Jdbc jdbc = proxy.findOne(Jdbc.class); // 获取jdbc对象
    final MyERPApp erpApp = new MyERPApp(jdbc);
}
